diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1af44ed7da..5872f6cbbd 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -56,6 +56,12 @@ _Does the change require QA team to:_ If any boxes above are checked the QA team will be automatically added as a PR reviewer. +## Up-port +[//]: # ( +This section is REQUIRED and should link to the PR that targets develop-3.0.0 branch. Assuming that the PR lands agains default develop-2.0.0 branch +If this is not needed, for example feature specific to NGOv2.X, then just mention this fact. +) + ## Backports [//]: # ( This section is REQUIRED and should link to the PR that targets other NGO version which is either develop or develop-2.0.0 branch diff --git a/.github/renovate.json5 b/.github/renovate.json5 index b3b212cd5b..31a5612ec8 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,5 +1,5 @@ { - "baseBranches": ["develop", "develop-2.0.0", "develop-3.x.x"], + "baseBranches": ["develop-2.0.0", "develop-3.x.x"], "dependencyDashboard": true, "$schema": "https://docs.renovatebot.com/renovate-schema.json", @@ -27,10 +27,12 @@ "unity-upm-package" ], "enabled": "true", - "schedule": [ - "every weekend" - ], + "schedule": ["every weekend"], "rollbackPrs": false, + }, + { + "groupName": "CI deps updates", + "matchPackagePatterns": ["^RecipeEngine", "actions/checkout", "package-ci/ubuntu-22.04"] } ], } diff --git a/.github/workflows/pr-description-validation.yml b/.github/workflows/pr-description-validation.yml index 050ca8cd7f..adb0b9dfda 100644 --- a/.github/workflows/pr-description-validation.yml +++ b/.github/workflows/pr-description-validation.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Check PR description uses: actions/github-script@v9 @@ -52,6 +52,10 @@ jobs: header: '## Backports', description: 'PR description must include a "## Backports" section. Please add this section and provide information about this PR backport to develop, develop-2.0.0 or develop-3.x.x branch respectively or explain why backport is not needed.' }, + { + header: '## Up-port', + description: 'PR description must include a "## Up-port" section. Please add this section and provide information about this PR up-port to develop-3.0.0 branch or explain why up-port is not needed.' + }, { header: '## Testing & QA', description: 'PR description must include a "## Testing & QA" section. Please add this section and provide information about the testing performed for this PR. It can range from adding unit tests to full samples and is needed from QA side to analyze PRs while Playtesting for the release.' diff --git a/.github/workflows/pr-supervisor.yaml b/.github/workflows/pr-supervisor.yaml index 0583d1b101..e2429a3a4e 100644 --- a/.github/workflows/pr-supervisor.yaml +++ b/.github/workflows/pr-supervisor.yaml @@ -26,8 +26,7 @@ jobs: timeout-minutes: 720 steps: - name: Checkout repository - uses: actions/checkout@v6 - + uses: actions/checkout@v7 - name: Wait and Verify Yamato Job Status env: GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.yamato/_triggers.yml b/.yamato/_triggers.yml index 6f2da59265..e86359bc3e 100644 --- a/.yamato/_triggers.yml +++ b/.yamato/_triggers.yml @@ -90,6 +90,8 @@ pr_code_changes_checks: # Note that our daily tests will anyway run both test configurations in "minimal supported" and "trunk" configurations - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_testproject_ubuntu_il2cpp_{{ pinnedTrunk }} - .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_testproject_ubuntu_il2cpp_{{ pinnedTrunk }} + # Run code coverage test + - .yamato/code-coverage.yml#code_coverage_ubuntu_{{ validation_editors.default }} triggers: expression: |- (pull_request.comment eq "ngo" OR @@ -159,6 +161,8 @@ develop_nightly: - .yamato/project-updated-dependencies-test.yml#updated-dependencies_testproject_NGO_win_6000.0 # Run API validation to early-detect all new APIs that would force us to release new minor version of the package. Note that for this to work the package version in package.json must correspond to "actual package state" which means that it should be higher than last released version - .yamato/vetting-test.yml#vetting_test + # Run code coverage test + - .yamato/code-coverage.yml#code_coverage_ubuntu_{{ validation_editors.default }} # Run all tests on weekly bases @@ -189,5 +193,3 @@ develop_weekly_trunk: - .yamato/_run-all.yml#run_all_webgl_builds # Run Runtime tests against CMB service - .yamato/_run-all.yml#run_all_project_tests_cmb_service - # Run code coverage test - - .yamato/code-coverage.yml#code_coverage_ubuntu_{{ validation_editors.default }} diff --git a/.yamato/code-coverage.yml b/.yamato/code-coverage.yml index caeeff72ec..64a2692225 100644 --- a/.yamato/code-coverage.yml +++ b/.yamato/code-coverage.yml @@ -39,7 +39,7 @@ code_coverage_{{ platform.name }}_{{ editor }}: commands: - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models - upm-pvp create-test-project test-project --packages "upm-ci~/packages/*.tgz" --unity .Editor - - UnifiedTestRunner --suite=editor --suite=playmode --editor-location=.Editor --testproject=test-project --enable-code-coverage coverage-upload-options="reportsDir:$PWD/test-results/CoverageResults;name:NGOv2_{{ platform.name }}_{{ editor }};flags:NGOv2_{{ platform.name }}_{{ editor }};verbose" --coverage-results-path=$PWD/test-results/CoverageResults --coverage-options="generateHtmlReport;generateAdditionalMetrics;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime" --extra-editor-arg=--burst-disable-compilation --timeout={{ test_timeout }} --rerun-strategy=Test --retry={{ num_test_retries }} --clean-library-on-rerun --artifacts-path=test-results + - UnifiedTestRunner --suite=editor --suite=playmode --editor-location=.Editor --testproject=test-project --enable-code-coverage --coverage-upload-options="reportsDir:$PWD/test-results/CoverageResults;name:NGOv2_{{ platform.name }}_{{ editor }};flags:NGOv2_{{ platform.name }}_{{ editor }};verbose" --coverage-results-path=$PWD/test-results/CoverageResults --coverage-options="generateHtmlReport;generateAdditionalMetrics;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime" --extra-editor-arg=--burst-disable-compilation --timeout={{ test_timeout }} --rerun-strategy=Test --retry={{ num_test_retries }} --clean-library-on-rerun --artifacts-path=test-results artifacts: logs: paths: diff --git a/.yamato/project.metafile b/.yamato/project.metafile index 439f5c5215..e839b89a74 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -24,7 +24,7 @@ small_agent_platform: - name: ubuntu type: Unity::VM - image: package-ci/ubuntu-22.04:v4.85.0 + image: package-ci/ubuntu-22.04:v4.87.0 flavor: b1.small @@ -39,13 +39,13 @@ test_platforms: default: - name: ubuntu type: Unity::VM - image: package-ci/ubuntu-22.04:v4.85.0 + image: package-ci/ubuntu-22.04:v4.87.0 flavor: b1.large standalone: StandaloneLinux64 desktop: - name: ubuntu type: Unity::VM - image: package-ci/ubuntu-22.04:v4.85.0 + image: package-ci/ubuntu-22.04:v4.87.0 flavor: b1.large smaller_flavor: b1.large larger_flavor: b1.xlarge @@ -117,12 +117,17 @@ test_platforms: # flavor: b1.large # larger_flavor: b1.xlarge # standalone: PS5 - - name: switch - type: Unity::VM - image: package-ci/win10-switch:v4 - flavor: b1.large - larger_flavor: b1.xlarge - standalone: Switch + # - name: switch --> TEMPORARILY DISABLED. SEE MTT-12118 + # The NintendoSDK on the win10-switch:v4 Bokken image is incomplete for the SDK + # version the newer editor Playback Engines require: 18_3_0 is missing the + # nintendo-switch-support lib the new toolchain links against, and 21_4_3 is missing the + # Tools\Graphics\NvnTools (GraphicsConverter) libraries needed to package textures. + # Neither is fixable from .yamato; re-enable once Package CI ships a complete SDK on the image. + # type: Unity::VM + # image: package-ci/win10-switch:v4 + # flavor: b1.large + # larger_flavor: b1.xlarge + # standalone: Switch - name: GameCoreXboxOne type: Unity::VM image: package-ci/win10-xbox:v4 @@ -148,13 +153,13 @@ test_platforms: # flavor: b1.large # larger_flavor: b1.xlarge # standalone: PS5 - - name: switch - type: Unity::console::switch - image: package-ci/win10-switch:v4 - flavor: b1.large - larger_flavor: b1.xlarge - standalone: Switch - base: win + # - name: switch --> TEMPORARILY DISABLED. SEE MTT-12118 (incomplete NintendoSDK on win10-switch:v4 image) + # type: Unity::console::switch + # image: package-ci/win10-switch:v4 + # flavor: b1.large + # larger_flavor: b1.xlarge + # standalone: Switch + # base: win - name: GameCoreXboxOne type: Unity::console::xbox image: package-ci/win10-xbox:v4 @@ -177,13 +182,13 @@ validation_editors: - 6000.3 all: - 6000.3 - - 6000.4 - 6000.5 + - 6000.6 - trunk - - f1298548e194f35ff7dfa6fee699d4464ab3919c + - 5fe7931aab8c4fff9274e15ef0800125c68b8d6a minimal: - 6000.0 -pinnedTrunk: f1298548e194f35ff7dfa6fee699d4464ab3919c +pinnedTrunk: 5fe7931aab8c4fff9274e15ef0800125c68b8d6a # Scripting backends used by Standalone RunTimeTests--------------------------------------------------- diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index 6976c10973..611902cc9a 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -60,8 +60,8 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index 06c750a127..a40678465b 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -29,5 +29,5 @@ package_pack_-_netcode_gameobjects: UPMCI_ACK_LARGE_PACKAGE: 1 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index 445f507a73..bafc711428 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -16,18 +16,18 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_3_-_macos13 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_3_-_ubuntu2204 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_3_-_win10 - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_4_-_macos13 - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_4_-_ubuntu2204 - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_4_-_win10 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_5_-_macos13 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_5_-_ubuntu2204 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_5_-_win10 - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_6_-_macos13 + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_6_-_macos13arm - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_6_-_ubuntu2204 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_6_-_win10 + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_7_-_macos13arm + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_7_-_ubuntu2204 + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_7_-_win10 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos13: @@ -82,10 +82,10 @@ preview_apv_-_6000_0_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu2204: @@ -140,10 +140,10 @@ preview_apv_-_6000_0_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_win10: @@ -199,10 +199,10 @@ preview_apv_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.3 manifest (MacOS). preview_apv_-_6000_3_-_macos13: @@ -257,10 +257,10 @@ preview_apv_-_6000_3_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.3 manifest (Ubuntu). preview_apv_-_6000_3_-_ubuntu2204: @@ -315,10 +315,10 @@ preview_apv_-_6000_3_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.3 manifest (Windows). preview_apv_-_6000_3_-_win10: @@ -374,14 +374,14 @@ preview_apv_-_6000_3_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 -# Functional tests for dependents found in the latest 6000.4 manifest (MacOS). -preview_apv_-_6000_4_-_macos13: - name: Preview APV - 6000.4 - macos13 +# Functional tests for dependents found in the latest 6000.5 manifest (MacOS). +preview_apv_-_6000_5_-_macos13: + name: Preview APV - 6000.5 - macos13 agent: image: package-ci/macos-13:v4 type: Unity::VM::osx @@ -394,10 +394,10 @@ preview_apv_-_6000_4_-_macos13: - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm timeout: 20 retries: 10 - - command: unity-downloader-cli -u 6000.4/staging -c editor --path .Editor --fast + - command: unity-downloader-cli -u 6000.5/staging -c editor --path .Editor --fast timeout: 10 retries: 3 - - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.4 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.5 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - command: bash .yamato/generated-scripts/infrastructure-instability-detection-mac.sh @@ -432,14 +432,14 @@ preview_apv_-_6000_4_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 -# Functional tests for dependents found in the latest 6000.4 manifest (Ubuntu). -preview_apv_-_6000_4_-_ubuntu2204: - name: Preview APV - 6000.4 - ubuntu2204 +# Functional tests for dependents found in the latest 6000.5 manifest (Ubuntu). +preview_apv_-_6000_5_-_ubuntu2204: + name: Preview APV - 6000.5 - ubuntu2204 agent: image: package-ci/ubuntu-22.04:v4 type: Unity::VM @@ -452,10 +452,10 @@ preview_apv_-_6000_4_-_ubuntu2204: - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm timeout: 20 retries: 10 - - command: unity-downloader-cli -u 6000.4/staging -c editor --path .Editor --fast + - command: unity-downloader-cli -u 6000.5/staging -c editor --path .Editor --fast timeout: 10 retries: 3 - - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.4 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.5 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh @@ -490,14 +490,14 @@ preview_apv_-_6000_4_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 -# Functional tests for dependents found in the latest 6000.4 manifest (Windows). -preview_apv_-_6000_4_-_win10: - name: Preview APV - 6000.4 - win10 +# Functional tests for dependents found in the latest 6000.5 manifest (Windows). +preview_apv_-_6000_5_-_win10: + name: Preview APV - 6000.5 - win10 agent: image: package-ci/win10:v4 type: Unity::VM @@ -511,11 +511,11 @@ preview_apv_-_6000_4_-_win10: - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm timeout: 20 retries: 10 - - command: unity-downloader-cli -u 6000.4/staging -c editor --path .Editor --fast + - command: unity-downloader-cli -u 6000.5/staging -c editor --path .Editor --fast timeout: 10 retries: 3 - - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.4 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - - command: python PythonScripts/editor_manifest_validator.py --version=6000.4 --wrench-config=.yamato/wrench/wrench_config.json + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.5 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/editor_manifest_validator.py --version=6000.5 --wrench-config=.yamato/wrench/wrench_config.json after: - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd artifacts: @@ -549,18 +549,19 @@ preview_apv_-_6000_4_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 -# Functional tests for dependents found in the latest 6000.5 manifest (MacOS). -preview_apv_-_6000_5_-_macos13: - name: Preview APV - 6000.5 - macos13 +# Functional tests for dependents found in the latest 6000.6 manifest (MacOS). +preview_apv_-_6000_6_-_macos13arm: + name: Preview APV - 6000.6 - macos13arm agent: - image: package-ci/macos-13:v4 + image: package-ci/macos-13-arm64:v4 type: Unity::VM::osx - flavor: b1.xlarge + flavor: m1.mac + model: M1 commands: - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip @@ -569,10 +570,10 @@ preview_apv_-_6000_5_-_macos13: - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm timeout: 20 retries: 10 - - command: unity-downloader-cli -u 6000.5/staging -c editor --path .Editor --fast + - command: unity-downloader-cli -u 6000.6/staging -c editor --path .Editor --fast --arch arm64 timeout: 10 retries: 3 - - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.5 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - command: bash .yamato/generated-scripts/infrastructure-instability-detection-mac.sh @@ -607,14 +608,14 @@ preview_apv_-_6000_5_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 -# Functional tests for dependents found in the latest 6000.5 manifest (Ubuntu). -preview_apv_-_6000_5_-_ubuntu2204: - name: Preview APV - 6000.5 - ubuntu2204 +# Functional tests for dependents found in the latest 6000.6 manifest (Ubuntu). +preview_apv_-_6000_6_-_ubuntu2204: + name: Preview APV - 6000.6 - ubuntu2204 agent: image: package-ci/ubuntu-22.04:v4 type: Unity::VM @@ -627,10 +628,10 @@ preview_apv_-_6000_5_-_ubuntu2204: - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm timeout: 20 retries: 10 - - command: unity-downloader-cli -u 6000.5/staging -c editor --path .Editor --fast + - command: unity-downloader-cli -u 6000.6/staging -c editor --path .Editor --fast timeout: 10 retries: 3 - - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.5 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh @@ -665,14 +666,14 @@ preview_apv_-_6000_5_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 -# Functional tests for dependents found in the latest 6000.5 manifest (Windows). -preview_apv_-_6000_5_-_win10: - name: Preview APV - 6000.5 - win10 +# Functional tests for dependents found in the latest 6000.6 manifest (Windows). +preview_apv_-_6000_6_-_win10: + name: Preview APV - 6000.6 - win10 agent: image: package-ci/win10:v4 type: Unity::VM @@ -686,11 +687,11 @@ preview_apv_-_6000_5_-_win10: - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm timeout: 20 retries: 10 - - command: unity-downloader-cli -u 6000.5/staging -c editor --path .Editor --fast + - command: unity-downloader-cli -u 6000.6/staging -c editor --path .Editor --fast timeout: 10 retries: 3 - - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.5 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - - command: python PythonScripts/editor_manifest_validator.py --version=6000.5 --wrench-config=.yamato/wrench/wrench_config.json + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/editor_manifest_validator.py --version=6000.6 --wrench-config=.yamato/wrench/wrench_config.json after: - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd artifacts: @@ -724,18 +725,19 @@ preview_apv_-_6000_5_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 -# Functional tests for dependents found in the latest 6000.6 manifest (MacOS). -preview_apv_-_6000_6_-_macos13: - name: Preview APV - 6000.6 - macos13 +# Functional tests for dependents found in the latest 6000.7 manifest (MacOS). +preview_apv_-_6000_7_-_macos13arm: + name: Preview APV - 6000.7 - macos13arm agent: - image: package-ci/macos-13:v4 + image: package-ci/macos-13-arm64:v4 type: Unity::VM::osx - flavor: b1.xlarge + flavor: m1.mac + model: M1 commands: - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip @@ -744,10 +746,10 @@ preview_apv_-_6000_6_-_macos13: - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm timeout: 20 retries: 10 - - command: unity-downloader-cli -u trunk -c editor --path .Editor --fast + - command: unity-downloader-cli -u trunk -c editor --path .Editor --fast --arch arm64 timeout: 10 retries: 3 - - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.7 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - command: bash .yamato/generated-scripts/infrastructure-instability-detection-mac.sh @@ -782,14 +784,14 @@ preview_apv_-_6000_6_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 -# Functional tests for dependents found in the latest 6000.6 manifest (Ubuntu). -preview_apv_-_6000_6_-_ubuntu2204: - name: Preview APV - 6000.6 - ubuntu2204 +# Functional tests for dependents found in the latest 6000.7 manifest (Ubuntu). +preview_apv_-_6000_7_-_ubuntu2204: + name: Preview APV - 6000.7 - ubuntu2204 agent: image: package-ci/ubuntu-22.04:v4 type: Unity::VM @@ -805,7 +807,7 @@ preview_apv_-_6000_6_-_ubuntu2204: - command: unity-downloader-cli -u trunk -c editor --path .Editor --fast timeout: 10 retries: 3 - - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.7 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh @@ -840,14 +842,14 @@ preview_apv_-_6000_6_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 -# Functional tests for dependents found in the latest 6000.6 manifest (Windows). -preview_apv_-_6000_6_-_win10: - name: Preview APV - 6000.6 - win10 +# Functional tests for dependents found in the latest 6000.7 manifest (Windows). +preview_apv_-_6000_7_-_win10: + name: Preview APV - 6000.7 - win10 agent: image: package-ci/win10:v4 type: Unity::VM @@ -864,8 +866,8 @@ preview_apv_-_6000_6_-_win10: - command: unity-downloader-cli -u trunk -c editor --path .Editor --fast timeout: 10 retries: 3 - - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - - command: python PythonScripts/editor_manifest_validator.py --version=6000.6 --wrench-config=.yamato/wrench/wrench_config.json + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.7 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/editor_manifest_validator.py --version=6000.7 --wrench-config=.yamato/wrench/wrench_config.json after: - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd artifacts: @@ -899,8 +901,8 @@ preview_apv_-_6000_6_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index 7fd22a018d..06dda4cdcf 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -91,102 +91,102 @@ publish_dry_run_netcode_gameobjects: UTR: location: results/UTR/validate-netcode.gameobjects-6000.3-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_4_-_macos13 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_macos13 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.4-macos13 + location: results/pvp/validate-netcode.gameobjects-6000.5-macos13 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.4-macos13 + location: results/UTR/validate-netcode.gameobjects-6000.5-macos13 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_4_-_ubuntu2204 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.4-ubuntu2204 + location: results/pvp/validate-netcode.gameobjects-6000.5-ubuntu2204 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.4-ubuntu2204 + location: results/UTR/validate-netcode.gameobjects-6000.5-ubuntu2204 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_4_-_win10 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_win10 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.4-win10 + location: results/pvp/validate-netcode.gameobjects-6000.5-win10 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.4-win10 + location: results/UTR/validate-netcode.gameobjects-6000.5-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_macos13 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_macos13arm specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.5-macos13 + location: results/pvp/validate-netcode.gameobjects-6000.6-macos13arm unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.5-macos13 + location: results/UTR/validate-netcode.gameobjects-6000.6-macos13arm unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.5-ubuntu2204 + location: results/pvp/validate-netcode.gameobjects-6000.6-ubuntu2204 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.5-ubuntu2204 + location: results/UTR/validate-netcode.gameobjects-6000.6-ubuntu2204 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_win10 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_win10 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.5-win10 + location: results/pvp/validate-netcode.gameobjects-6000.6-win10 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.5-win10 + location: results/UTR/validate-netcode.gameobjects-6000.6-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_macos13 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_macos13arm specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.6-macos13 + location: results/pvp/validate-netcode.gameobjects-6000.7-macos13arm unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.6-macos13 + location: results/UTR/validate-netcode.gameobjects-6000.7-macos13arm unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_ubuntu2204 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.6-ubuntu2204 + location: results/pvp/validate-netcode.gameobjects-6000.7-ubuntu2204 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.6-ubuntu2204 + location: results/UTR/validate-netcode.gameobjects-6000.7-ubuntu2204 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_win10 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_win10 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.6-win10 + location: results/pvp/validate-netcode.gameobjects-6000.7-win10 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.6-win10 + location: results/UTR/validate-netcode.gameobjects-6000.7-win10 unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -273,101 +273,101 @@ publish_netcode_gameobjects: UTR: location: results/UTR/validate-netcode.gameobjects-6000.3-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_4_-_macos13 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_macos13 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.4-macos13 + location: results/pvp/validate-netcode.gameobjects-6000.5-macos13 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.4-macos13 + location: results/UTR/validate-netcode.gameobjects-6000.5-macos13 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_4_-_ubuntu2204 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.4-ubuntu2204 + location: results/pvp/validate-netcode.gameobjects-6000.5-ubuntu2204 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.4-ubuntu2204 + location: results/UTR/validate-netcode.gameobjects-6000.5-ubuntu2204 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_4_-_win10 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_win10 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.4-win10 + location: results/pvp/validate-netcode.gameobjects-6000.5-win10 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.4-win10 + location: results/UTR/validate-netcode.gameobjects-6000.5-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_macos13 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_macos13arm specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.5-macos13 + location: results/pvp/validate-netcode.gameobjects-6000.6-macos13arm unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.5-macos13 + location: results/UTR/validate-netcode.gameobjects-6000.6-macos13arm unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.5-ubuntu2204 + location: results/pvp/validate-netcode.gameobjects-6000.6-ubuntu2204 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.5-ubuntu2204 + location: results/UTR/validate-netcode.gameobjects-6000.6-ubuntu2204 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_win10 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_win10 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.5-win10 + location: results/pvp/validate-netcode.gameobjects-6000.6-win10 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.5-win10 + location: results/UTR/validate-netcode.gameobjects-6000.6-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_macos13 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_macos13arm specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.6-macos13 + location: results/pvp/validate-netcode.gameobjects-6000.7-macos13arm unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.6-macos13 + location: results/UTR/validate-netcode.gameobjects-6000.7-macos13arm unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_ubuntu2204 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.6-ubuntu2204 + location: results/pvp/validate-netcode.gameobjects-6000.7-ubuntu2204 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.6-ubuntu2204 + location: results/UTR/validate-netcode.gameobjects-6000.7-ubuntu2204 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_win10 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_win10 specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.6-win10 + location: results/pvp/validate-netcode.gameobjects-6000.7-win10 unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.6-win10 + location: results/UTR/validate-netcode.gameobjects-6000.7-win10 unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 allow_on: branch match "^release/.*" metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index b9eba06e54..343a1a9b85 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -31,5 +31,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index 84a92fcd75..88d03e589b 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -69,10 +69,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -139,10 +139,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -209,10 +209,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -279,10 +279,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -349,10 +349,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -419,16 +419,16 @@ validate_-_netcode_gameobjects_-_6000_3_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.4 - macos13 (6000.4 - MacOS). -validate_-_netcode_gameobjects_-_6000_4_-_macos13: - name: Validate - netcode.gameobjects - 6000.4 - macos13 +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.5 - macos13 (6000.5 - MacOS). +validate_-_netcode_gameobjects_-_6000_5_-_macos13: + name: Validate - netcode.gameobjects - 6000.5 - macos13 agent: image: package-ci/macos-13:v4 type: Unity::VM::osx @@ -438,7 +438,7 @@ validate_-_netcode_gameobjects_-_6000_4_-_macos13: - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py - - command: unity-downloader-cli -u 6000.4/staging -c editor --path .Editor --fast + - command: unity-downloader-cli -u 6000.5/staging -c editor --path .Editor --fast timeout: 20 retries: 3 - command: upm-pvp create-test-project testproject --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects com.unity.netcode.gameobjects.tests" --unity .Editor @@ -489,16 +489,16 @@ validate_-_netcode_gameobjects_-_6000_4_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.4 - ubuntu2204 (6000.4 - Ubuntu). -validate_-_netcode_gameobjects_-_6000_4_-_ubuntu2204: - name: Validate - netcode.gameobjects - 6000.4 - ubuntu2204 +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.5 - ubuntu2204 (6000.5 - Ubuntu). +validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204: + name: Validate - netcode.gameobjects - 6000.5 - ubuntu2204 agent: image: package-ci/ubuntu-22.04:v4 type: Unity::VM @@ -508,7 +508,7 @@ validate_-_netcode_gameobjects_-_6000_4_-_ubuntu2204: - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py - - command: unity-downloader-cli -u 6000.4/staging -c editor --path .Editor --fast + - command: unity-downloader-cli -u 6000.5/staging -c editor --path .Editor --fast timeout: 20 retries: 3 - command: upm-pvp create-test-project testproject --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects com.unity.netcode.gameobjects.tests" --unity .Editor @@ -559,16 +559,16 @@ validate_-_netcode_gameobjects_-_6000_4_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.4 - win10 (6000.4 - Windows). -validate_-_netcode_gameobjects_-_6000_4_-_win10: - name: Validate - netcode.gameobjects - 6000.4 - win10 +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.5 - win10 (6000.5 - Windows). +validate_-_netcode_gameobjects_-_6000_5_-_win10: + name: Validate - netcode.gameobjects - 6000.5 - win10 agent: image: package-ci/win10:v4 type: Unity::VM @@ -578,7 +578,7 @@ validate_-_netcode_gameobjects_-_6000_4_-_win10: - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py - - command: unity-downloader-cli -u 6000.4/staging -c editor --path .Editor --fast + - command: unity-downloader-cli -u 6000.5/staging -c editor --path .Editor --fast timeout: 20 retries: 3 - command: upm-pvp create-test-project testproject --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects com.unity.netcode.gameobjects.tests" --unity .Editor @@ -629,26 +629,27 @@ validate_-_netcode_gameobjects_-_6000_4_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.5 - macos13 (6000.5 - MacOS). -validate_-_netcode_gameobjects_-_6000_5_-_macos13: - name: Validate - netcode.gameobjects - 6000.5 - macos13 +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.6 - macos13arm (6000.6 - MacOS). +validate_-_netcode_gameobjects_-_6000_6_-_macos13arm: + name: Validate - netcode.gameobjects - 6000.6 - macos13arm agent: - image: package-ci/macos-13:v4 + image: package-ci/macos-13-arm64:v4 type: Unity::VM::osx - flavor: b1.xlarge + flavor: m1.mac + model: M1 commands: - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py - - command: unity-downloader-cli -u 6000.5/staging -c editor --path .Editor --fast + - command: unity-downloader-cli -u 6000.6/staging -c editor --path .Editor --fast --arch arm64 timeout: 20 retries: 3 - command: upm-pvp create-test-project testproject --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects com.unity.netcode.gameobjects.tests" --unity .Editor @@ -699,16 +700,16 @@ validate_-_netcode_gameobjects_-_6000_5_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.5 - ubuntu2204 (6000.5 - Ubuntu). -validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204: - name: Validate - netcode.gameobjects - 6000.5 - ubuntu2204 +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.6 - ubuntu2204 (6000.6 - Ubuntu). +validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204: + name: Validate - netcode.gameobjects - 6000.6 - ubuntu2204 agent: image: package-ci/ubuntu-22.04:v4 type: Unity::VM @@ -718,7 +719,7 @@ validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204: - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py - - command: unity-downloader-cli -u 6000.5/staging -c editor --path .Editor --fast + - command: unity-downloader-cli -u 6000.6/staging -c editor --path .Editor --fast timeout: 20 retries: 3 - command: upm-pvp create-test-project testproject --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects com.unity.netcode.gameobjects.tests" --unity .Editor @@ -769,16 +770,16 @@ validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.5 - win10 (6000.5 - Windows). -validate_-_netcode_gameobjects_-_6000_5_-_win10: - name: Validate - netcode.gameobjects - 6000.5 - win10 +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.6 - win10 (6000.6 - Windows). +validate_-_netcode_gameobjects_-_6000_6_-_win10: + name: Validate - netcode.gameobjects - 6000.6 - win10 agent: image: package-ci/win10:v4 type: Unity::VM @@ -788,7 +789,7 @@ validate_-_netcode_gameobjects_-_6000_5_-_win10: - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py - - command: unity-downloader-cli -u 6000.5/staging -c editor --path .Editor --fast + - command: unity-downloader-cli -u 6000.6/staging -c editor --path .Editor --fast timeout: 20 retries: 3 - command: upm-pvp create-test-project testproject --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects com.unity.netcode.gameobjects.tests" --unity .Editor @@ -839,26 +840,27 @@ validate_-_netcode_gameobjects_-_6000_5_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.6 - macos13 (6000.6 - MacOS). -validate_-_netcode_gameobjects_-_6000_6_-_macos13: - name: Validate - netcode.gameobjects - 6000.6 - macos13 +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.7 - macos13arm (6000.7 - MacOS). +validate_-_netcode_gameobjects_-_6000_7_-_macos13arm: + name: Validate - netcode.gameobjects - 6000.7 - macos13arm agent: - image: package-ci/macos-13:v4 + image: package-ci/macos-13-arm64:v4 type: Unity::VM::osx - flavor: b1.xlarge + flavor: m1.mac + model: M1 commands: - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py - - command: unity-downloader-cli -u trunk -c editor --path .Editor --fast + - command: unity-downloader-cli -u trunk -c editor --path .Editor --fast --arch arm64 timeout: 20 retries: 3 - command: upm-pvp create-test-project testproject --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects com.unity.netcode.gameobjects.tests" --unity .Editor @@ -909,16 +911,16 @@ validate_-_netcode_gameobjects_-_6000_6_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.6 - ubuntu2204 (6000.6 - Ubuntu). -validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204: - name: Validate - netcode.gameobjects - 6000.6 - ubuntu2204 +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.7 - ubuntu2204 (6000.7 - Ubuntu). +validate_-_netcode_gameobjects_-_6000_7_-_ubuntu2204: + name: Validate - netcode.gameobjects - 6000.7 - ubuntu2204 agent: image: package-ci/ubuntu-22.04:v4 type: Unity::VM @@ -979,16 +981,16 @@ validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.6 - win10 (6000.6 - Windows). -validate_-_netcode_gameobjects_-_6000_6_-_win10: - name: Validate - netcode.gameobjects - 6000.6 - win10 +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.7 - win10 (6000.7 - Windows). +validate_-_netcode_gameobjects_-_6000_7_-_win10: + name: Validate - netcode.gameobjects - 6000.7 - win10 agent: image: package-ci/win10:v4 type: Unity::VM @@ -1049,10 +1051,10 @@ validate_-_netcode_gameobjects_-_6000_6_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index 9aa8ecfde1..96b58ebf4f 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -39,7 +39,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "2.9.0.0", + "wrench_version": "3.2.1.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools/CI/NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index 452a22131a..e47057e13d 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,11 +8,11 @@ - - - - - + + + + + diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 3a258e2a87..a9922a19b6 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,11 +10,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added -- Added support for Unity's Fast Enter Play Mode with domain reload disabled. (#3956) ### Changed -- Changed replaced define usages of `DEVELOPMENT_BUILD || UNITY_EDITOR` and a few niche uses of `DEVELOPMENT_BUILD` with `DEBUG`. (#4006) - All editor assembly definitions are renamed with `Unity.Netcode.GameObjects.x` variants - `Unity.Netcode.Editor` → `Unity.Netcode.GameObjects.Editor` - `Unity.Netcode.Editor.CodeGen` → `Unity.Netcode.GameObjects.Editor.CodeGen` @@ -23,7 +21,6 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Deprecated -- Deprecated the nullable boolean `NetworkObject.IsSceneObject` and introduced `NetworkObject.InScenePlaced`. (#4000) ### Removed @@ -37,6 +34,39 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Obsolete +## [2.13.1] - 2026-07-19 + +### Added + +- Single player session section to provide users with information about `SinglePlayerTransport` and an example script of how to switch between single and multi player sessions. (#4062) + +### Fixed + +- Issue where `NetworkAnimator` did no bounds check on the parameter index read prior to obtaining a pointer to the location within the array. (#4090) +- Issue where scene migration in a distributed authority session would throw an exception on clients migrating their owned `NetworkObject` instances to a different scene.(#4086) +- Issue where migrating a dynamically spawned or in-scene placed `NetworkObject` into a different scene during awake could result in that spawned instance to not be synchronized. (#4086) +- Issue where a NullReferenceException was thrown when a non-authority failed to spawn a NetworkObject. (#4067) +- Issue where the active scene was not being serialized as the 1st scene which could result in various errors including a soft synchronization error if, on the client-side, other synchronized scenes had already been loaded prior to the active scene, which is always loaded as `LoadSceneMode.SingleMode` when client synchronization is set to `LoadSceneMode.SingleMode`, resulting in the previously loaded scene(s) to be unloaded. (#4065) +- Issue when FastBufferReader is attempting to read a string and all or a portion of the character count has already been read by user script, it could read a character length that results in a negative byte length which could result in an editor crash. (#4052) +- Issue where NetworkRigidbodyBase was not applying rotation correctly when using Rigidbody2D. (#4012) +- Issue where NetworkRigidbodyBase was always checking the 3D rigid body's interpolation mode when determining if it is kinematic and needs to put the rigid body to sleep and then switch to interpolation. (#4012) +- Fixed AnticipatedNetworkTransform not respecting the InLocalSpace flag. (#3995) + +## [2.13.0] - 2026-06-21 + +### Added + +- Added support for Unity's Fast Enter Play Mode with domain reload disabled. (#3956) +- RPC messages now log any time they are not processed. (#3994) + +### Changed + +- Changed replaced define usages of `DEVELOPMENT_BUILD || UNITY_EDITOR` and a few niche uses of `DEVELOPMENT_BUILD` with `DEBUG`. (#4006) + +### Deprecated + +- Deprecated the nullable boolean `NetworkObject.IsSceneObject` and introduced `NetworkObject.InScenePlaced`. (#4000) + ## [2.12.0] - 2026-05-24 ### Added diff --git a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md index e78e7192f6..24219a83e5 100644 --- a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md +++ b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md @@ -18,6 +18,7 @@ * [Connection approval](basics/connection-approval.md) * [Max players](basics/maxnumberplayers.md) * [Transports](advanced-topics/transports.md) + * [Single player sessions](advanced-topics/singleplayer.md) * [Unity Relay](relay/relay.md) * [Command-line arguments](command-line-arguments.md) * [Network components](network-components.md) @@ -70,15 +71,17 @@ * [Network update loop reference](advanced-topics/network-update-loop-system/network-update-loop-reference.md) * [Network time and ticks](advanced-topics/networktime-ticks.md) * [Serialization](serialization.md) + * [Serialization overview](advanced-topics/serialization/serialization-overview.md) * [C# primitives](advanced-topics/serialization/cprimitives.md) * [Unity primitives](advanced-topics/serialization/unity-primitives.md) * [Enum types](advanced-topics/serialization/enum-types.md) * [Arrays](advanced-topics/serialization/serialization-arrays.md) - * [INetworkSerializable](advanced-topics/serialization/inetworkserializable.md) - * [INetworkSerializeByMemcpy](advanced-topics/serialization/inetworkserializebymemcpy.md) - * [Custom serialization](advanced-topics/custom-serialization.md) - * [NetworkObject serialization](advanced-topics/serialization/networkobject-serialization.md) + * [Customize serializable types with INetworkSerializable](advanced-topics/serialization/inetworkserializable.md) + * [Serialize unmanaged structs with INetworkSerializeByMemcpy](advanced-topics/serialization/inetworkserializebymemcpy.md) + * [NetworkObject and NetworkBehaviour serialization](advanced-topics/serialization/networkobject-serialization.md) * [FastBufferWriter and FastBufferReader](advanced-topics/fastbufferwriter-fastbufferreader.md) + * [BufferSerializer](advanced-topics/bufferserializer.md) + * [Custom serialization](advanced-topics/custom-serialization.md) * [Scene management](scene-management.md) * [Scene management overview](basics/scenemanagement/scene-management-overview.md) * [Integrated management](integrated-management.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/bufferserializer.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/bufferserializer.md index 22375bccf5..0f5df51f15 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/bufferserializer.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/bufferserializer.md @@ -1,5 +1,8 @@ # BufferSerializer +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning how to use `BufferSerializer`. + `BufferSerializer` is the bi-directional serializer primarily used for serializing within [`INetworkSerializable`](serialization/inetworkserializable.md) types. It wraps [`FastBufferWriter` and `FastBufferReader`](fastbufferwriter-fastbufferreader.md) to provide high performance serialization, but has a couple of differences to make it more user-friendly: - Rather than writing separate methods for serializing and deserializing, `BufferSerializer` allows writing a single method that can handle both operations, which reduces the possibility of a mismatch between the two @@ -15,3 +18,7 @@ However, when those downsides are unreasonable, `BufferSerializer - For performance, you can use `PreCheck(int amount)` followed by `SerializeValuePreChecked()` to perform bounds checking for multiple fields at once. - For both performance and bandwidth usage, you can obtain the wrapped underlying reader/writer via `serializer.GetFastBufferReader()` when `serializer.IsReader` is `true`, and `serializer.GetFastBufferWriter()` when `serializer.IsWriter` is `true`. These provide micro-performance improvements by removing a level of indirection, and also give you a type you can use with `BytePacker` and `ByteUnpacker`. + +## Serializing custom types + +`BufferSerializer` can be extended via extension methods to handle serializing custom types. Refer to [customizing `BufferSerializer`](./custom-serialization.md#bufferserializer) for instructions on how to do this. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md index 7a04e2f123..b7b23e4afc 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md @@ -1,93 +1,71 @@ # Custom serialization -Netcode uses a default serialization pipeline when using `RPC`s, `NetworkVariable`s, or any other Netcode-related tasks that require serialization. The serialization pipeline looks like this: +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning how to customize serialization. -`` -Custom Types => Built In Types => INetworkSerializable -`` +Netcode for GameObjects provide support for serializing any unsupported types, and with the API provided, it can even be done with types that you haven't defined yourself, those who are behind a 3rd party wall, such as .NET types. However, the way custom serialization is implemented for RPCs and NetworkVariables is slightly different. -That is, when Netcode first gets hold of a type, it will check for any custom types that the user have registered for serialization, after that it will check if it's a built in type, such as a Vector3, float etc. These are handled by default. If not, it will check if the type inherits `INetworkSerializable`, if it does, it will call it's write methods. +Netcode for GameObjects supports custom serialization of unsupported types, including those you haven't defined yourself, such as third-party .NET types. You can also use custom serialization to override how an existing supported type is serialized. -By default, any type that satisfies the `unmanaged` generic constraint can be automatically serialized as RPC parameters. This includes all basic types (bool, byte, int, float, enum, etc) as well as any structs that has only these basic types. +Custom serialization is implemented slightly differently for RPCs and NetworkVariables. The examples on this page provide different ways to serialization a custom health struct. -With this flow, you can provide support for serializing any unsupported types, and with the API provided, it can even be done with types that you haven't defined yourself, those who are behind a 3rd party wall, such as .NET types. However, the way custom serialization is implemented for RPCs and NetworkVariables is slightly different. +[!code-cs[](../../Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs#HealthStruct)] -### Serialize a type in a Remote Procedure Call (RPC) +## FastBufferReader and FastBufferWriter -> [!NOTE] -> From versioln 1.7.0 Remote Procedure Calls (RPCs) can also use the Network Variable flow, but NetworkVariables can't use the RPC flow. The RPC flow is more efficient when RPCs serialize the type. Unity selects the RPC flow if you implement both the RPC and Network variable flows. When a type is used by both NetworkVariables and RPCs you can use the NetworkVariable flow to lower maintenance requirements. +[`FastBufferReader` and `FastBufferWriter`](./fastbufferwriter-fastbufferreader.md) are the main serialization tools in Netcode for GameObjects. To register serialization for a custom type, or override an already handled type, you need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()`. `FastBufferReader` and `FastBufferWriter` can read and write primitive types, and you can extend this functionality to serialize your custom type. -To register a custom type, or override an already handled type, you need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()`: +[!code-cs[](../../Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs#FastBuffer)] -```csharp -// Tells the Netcode how to serialize and deserialize Url in the future. -// The class name doesn't matter here. -public static class SerializationExtensions -{ - public static void ReadValueSafe(this FastBufferReader reader, out Url url) - { - reader.ReadValueSafe(out string val); - url = new Url(val); - } - - public static void WriteValueSafe(this FastBufferWriter writer, in Url url) - { - writer.WriteValueSafe(url.Value); - } -} -``` +You may also need to add extensions for `FastBufferReader.ReadValue()`, `FastBufferWriter.WriteValue()` if you want to serialize without [bounds checking](./fastbufferwriter-fastbufferreader.md#bounds-checking) -The code generation for RPCs will automatically pick up and use these functions, and they'll become available via `FastBufferWriter` and `FastBufferReader` directly. +## BufferSerializer -You can also optionally use the same method to add support for `BufferSerializer.SerializeValue()`, if you wish, which will make this type readily available within [`INetworkSerializable`](serialization/inetworkserializable.md) types: +You can also add custom serialization support to the bi-directional [`BufferSerializer`](./bufferserializer.md). This makes your custom type readily available within [`INetworkSerializable`](serialization/inetworkserializable.md) types and in the [`NetworkBehaviour.OnSynchronize()` method](../components/core/networkbehaviour-synchronize.md#prespawn-synchronization-with-onsynchronize): -```csharp -// The class name doesn't matter here. -public static class SerializationExtensions -{ - public static void SerializeValue(this BufferSerializer serializer, ref Url url) where TReaderWriter: IReaderWriter - { - if (serializer.IsReader) - { - url = new Url(); - } - serializer.SerializeValue(ref url.Value); - } -} -``` +[!code-cs[](../../Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs#BufferSerializer)] -Additionally, you can also add extensions for `FastBufferReader.ReadValue()`, `FastBufferWriter.WriteValue()`, and `BufferSerializer.SerializeValuePreChecked()` to provide more optimal implementations for manual serialization using `FastBufferReader.TryBeginRead()`, `FastBufferWriter.TryBeginWrite()`, and `BufferSerializer.PreCheck()`, respectively. However, none of these will be used for serializing RPCs - only `ReadValueSafe` and `WriteValueSafe` are used. +## Remote procedure call (RPCs) -### For NetworkVariable +> [!NOTE] +> RPCs can use the Network Variable flow, but NetworkVariables can't use the RPC flow. The RPC flow is more efficient when only RPCs need to serialize the type. When a type is used by both NetworkVariables and RPCs, you can implement just the NetworkVariable flow to lower maintenance requirements. Unity will select the RPC flow for RPCs if you have implemented both flows. -`NetworkVariable` goes through a slightly different pipeline than `RPC`s and relies on a different process for determining how to serialize its types. As a result, making a custom type available to the `RPC` pipeline doesn't automatically make it available to the `NetworkVariable` pipeline, and vice-versa. The same method can be used for both, but currently, `NetworkVariable` requires an additional runtime step to make it aware of the methods. +To serialize a custom type, or override an already handled type, you need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()` as [outlined above](#fastbufferreader-and-fastbufferwriter). -To add custom serialization support in `NetworkVariable`, follow the steps from the "For RPCs" section to write extension methods for `FastBufferReader` and `FastBufferWriter`; then, somewhere in your application startup (before any `NetworkVariable`s using the affected types will be serialized) add the following: +The code generation for RPCs will automatically pick up and use these functions, as they'll become available via `FastBufferWriter` and `FastBufferReader` directly. -```csharp -UserNetworkVariableSerialization.WriteValue = SerializationExtensions.WriteValueSafe; -UserNetworkVariableSerialization.ReadValue = SerializationExtensions.ReadValueSafe; -``` +## NetworkVariable + +Implementing [`INetworkSerializable`](./serialization/inetworkserializable.md) is the cleanest and most straightforward way to customize the serialization of a type within a [`NetworkVariable`](../basics/networkvariable.md). `UserNetworkVariableSerialization` provides runtime configuration to further override serialization of a type. -You can also use lambda expressions here: +First you will need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()` as [outlined above](#fastbufferreader-and-fastbufferwriter). + +Secondly, somewhere in your application startup (before any `NetworkVariable`s using the affected types will be serialized), add the following: ```csharp -UserNetworkVariableSerialization.WriteValue = (FastBufferWriter writer, in Url url) => -{ - writer.WriteValueSafe(url.Value); -}; - -UserNetworkVariableSerialization.ReadValue = (FastBufferReader reader, out Url url) -{ - reader.ReadValueSafe(out string val); - url = new Url(val); -}; +UserNetworkVariableSerialization.WriteValue = SerializationExtensions.WriteValueSafe; +UserNetworkVariableSerialization.ReadValue = SerializationExtensions.ReadValueSafe; +UserNetworkVariableSerialization.DuplicateValue = (in Health value, ref Health duplicatedValue) => duplicatedValue = value; ``` -When you create an extension method in `NetworkVariable` you need to implement the following values: +`DuplicateValue` should return a complete deep copy of the value that `NetworkVariable` compares to a previous value, which is used to check whether the value has changed. `DuplicateValue` avoids re-serializing it over the network every frame when it hasn't changed. + +> [!NOTE] +> `WriteValue`, `ReadValue` and `DuplicateValue` all need to be defined to customize your serialization. + +> [!NOTE] +> `WriteValue` and `ReadValue` will not be used if a type implements `INetworkSerializable` or [`INetworkSerializeByMemcpy`](./serialization/inetworkserializebymemcpy.md). + +### Serializing delta updates + +Reading and writing a value provides the minimal amount of `NetworkVariable` functionality. This will synchronize your whole type whenever any value within the type value changes. To support sending delta updates rather than a full update whenever your type has changed, implement the following functions: + +- `WriteDelta` +- `ReadDelta` + +> [!NOTE] +> Both `WriteDelta` and `ReadDelta` need to be defined for either to be used. -- `WriteValue` -- `ReadValue` -- `DuplicateValue` +Here is a full implementation of a custom type with the methods needed for `UserNetworkVariableSerialization` -`DuplicateValue` returns a complete deep copy of the value that `NetworkVariable` compares to a previous value to check whether or not that values has changed. This avoids reserializing it over the network every frame when it hasn't changed. +[!code-cs[](../../Tests/Runtime/DocumentationCodeSamples/NetworkVariable/NetworkVariableSerialization.cs#HealthExample)] diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md index 1ac9b0f9d3..3f8327fc17 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md @@ -1,5 +1,8 @@ # FastBufferWriter and FastBufferReader +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning about `FastBufferWriter` and `FastBufferReader`. + The serialization and deserialization is done via `FastBufferWriter` and `FastBufferReader`. These have methods for serializing individual types and methods for serializing packed numbers, but in particular provide a high-performance method called `WriteValue()/ReadValue()` (for Writers and Readers, respectively) that can extremely quickly write an entire unmanaged struct to a buffer. There's a trade-off of CPU usage vs bandwidth in using this: Writing individual fields is slower (especially when it includes operations on unaligned memory), but allows the buffer to be filled more efficiently, both because it avoids padding for alignment in structs, and because it allows you to use `BytePacker.WriteValuePacked()`/`ByteUnpacker.ReadValuePacked()` and `BytePacker.WriteValueBitPacked()`/`ByteUnpacker.ReadValueBitPacked()`. The difference between these two is that the BitPacked variants pack more efficiently, but they reduce the valid range of values. See the section below for details on packing. @@ -156,3 +159,7 @@ Packing values is done using the utility classes `BytePacker` and `ByteUnpacker` | uint | 30 bits (0 to 1,073,741,824) | | long | 60 bits + sign bit (-1,152,921,504,606,846,976 to 1,152,921,504,606,846,975) | | ulong | 61 bits (0 to 2,305,843,009,213,693,952) | + +## Serializing custom types + +`FastBufferReader` and `FastBufferWriter` can be extended via extension methods to handle serializing custom types. Refer to [customizing `FastBufferReader` and `FastBufferWriter`](./custom-serialization.md#fastbufferreader-and-fastbufferwriter) for instructions on how to do this. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md index 1fb5c3dcc4..b46a1b8b91 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md @@ -328,3 +328,4 @@ void Update() ## Additional resources * [RPC parameters](rpc-params.md) +* [Customizing serialization](../custom-serialization.md#remote-procedure-call-rpc) diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md index 36d83416ba..2733a0e373 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md @@ -1,9 +1,14 @@ -# INetworkSerializable +# Customize serializable types with INetworkSerializable -You can use the `INetworkSerializable` interface to define custom serializable types. +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before customizing serializable types with `INetworkSerializable`. + +You can use the `INetworkSerializable` interface to define custom serializable types. This interface has one function: `NetworkSerialize(BufferSerializer serializer)`, which ingests a bi-directional [`BufferSerializer`](../bufferserializer.md) that you can use to implement bi-directional custom serialization. + +`INetworkSerializable` can be implemented on both managed and unmanaged types, although serializing managed types isn't recommended because it can reduce your game's performance. ```csharp -struct MyComplexStruct : INetworkSerializable +struct SpawnPoint : INetworkSerializable { public Vector3 Position; public Quaternion Rotation; @@ -18,24 +23,20 @@ struct MyComplexStruct : INetworkSerializable } ``` -Types implementing `INetworkSerializable` are supported by `NetworkSerializer`, `RPC` s and `NetworkVariable` s. +Types implementing `INetworkSerializable` are supported by [`FastBufferReader` and `FastBufferWriter`](./fastbufferwriter-fastbufferreader.md), [`RPC`s'](../message-system/rpc.md), and [`NetworkVariable`s](../../basics/networkvariable.md). ```csharp - [Rpc(SendTo.Server)] -void MyServerRpc(MyComplexStruct myStruct) { /* ... */ } +void SpawnAtPointRpc(SpawnPoint spawnPoint) { /* ... */ } -void Update() +void DoSpawnHere() { - if (Input.GetKeyDown(KeyCode.P)) + var spawnPoint = new SpawnPoint { - MyServerRpc( - new MyComplexStruct - { - Position = transform.position, - Rotation = transform.rotation - }); // Client -> Server - } + Position = transform.position, + Rotation = transform.rotation + }; + SpawnAtPointRpc(spawnPoint); // Client -> Server } ``` @@ -59,7 +60,7 @@ The following example explores a more advanced use case. ```csharp -public struct MyMoveStruct : INetworkSerializable +public struct SpawnWithMovement : INetworkSerializable { public Vector3 Position; public Quaternion Rotation; @@ -125,7 +126,7 @@ Review the following example: ```csharp -public struct MyStructA : INetworkSerializable +public struct SpawnPoint : INetworkSerializable { public Vector3 Position; public Quaternion Rotation; @@ -137,17 +138,17 @@ public struct MyStructA : INetworkSerializable } } -public struct MyStructB : INetworkSerializable +public struct SpawnInfo : INetworkSerializable { public int SomeNumber; public string SomeText; - public MyStructA StructA; + public SpawnPoint SpawnPoint; void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { serializer.SerializeValue(ref SomeNumber); serializer.SerializeValue(ref SomeText); - StructA.NetworkSerialize(serializer); + SpawnPoint.NetworkSerialize(serializer); } } ``` @@ -167,7 +168,7 @@ While you can have nested `INetworkSerializable` implementations (an `INetworkSe ```csharp /// This isn't supported. -public struct MyStructB : MyStructA +public struct SpawnInfo : SpawnPoint { public int SomeNumber; public string SomeText; @@ -232,4 +233,4 @@ Then declare this network variable like so: ```csharp NetworkVariable myVar = new NetworkVariable(); -``` \ No newline at end of file +``` diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md index 7a684dbaba..212bfbf339 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md @@ -1,4 +1,7 @@ -# INetworkSerializeByMemcpy +# Serialize unmanaged structs with INetworkSerializeByMemcpy + +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before using `INetworkSerializeByMemcpy` to serialize unmanaged structs. The `INetworkSerializeByMemcpy` interface is used to mark an unmanaged struct type as being trivially serializable over the network by directly copying the whole struct, byte-for-byte, as it appears in memory, into and out of the buffer. This can offer some benefits for performance compared to serializing one field at a time, especially if the struct has many fields in it, but it may be less efficient from a bandwidth-usage perspective, as fields will often be padded for memory alignment and you won't be able to "pack" any of the fields to optimize for space usage. @@ -14,7 +17,7 @@ public struct MyStruct : INetworkSerializeByMemcpy } ``` -If you have a type you wish to serialize that you know is compatible with this method of serialization, but don't have access to modify the struct to add this interface, you can wrap your values in `ForceNetworkSerializeByMemcpy` to enable it to be serialized this way. This works in both `RPC`s and `NetworkVariables`, as well as in other contexts such as `BufferSerializer<>`, `FastBufferReader`, and `FastBufferWriter`. +If you have a type you wish to serialize that you know is compatible with this method of serialization, but don't have access to modify the struct to add this interface, you can wrap your values in `ForceNetworkSerializeByMemcpy` to enable it to be serialized this way. This works in both [`RPC`s](../message-system/rpc.md) and [`NetworkVariable`s](../../basics/networkvariable.md), as well as in other contexts such as with [`BufferSerializer<>`](../bufferserializer.md) or [`FastBufferReader`, and `FastBufferWriter`](../fastbufferwriter-fastbufferreader.md). ```csharp public NetworkVariable> GuidVar; diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md index 9ca778028d..466d545b4b 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md @@ -1,14 +1,18 @@ -# NetworkObject and NetworkBehaviour +# NetworkObject and NetworkBehaviour serialization -`GameObjects`, `NetworkObjects` and NetworkBehaviour aren't serializable types so they can't be used in `RPCs` or `NetworkVariables` by default. +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning how to serialize `NetworkObjects` and `NetworkBehaviours`. + +`GameObject`, [`NetworkObject`](../../components/core/networkobject.md) and [`NetworkBehaviour`](../../components/core/networkbehaviour.md) aren't serializable types so they can't be used in [`RPC`s](../message-system/rpc.md) or [`NetworkVariable`s](../../basics/networkvariable.md) by default. -There are two convenience wrappers which can be used to send a reference to a NetworkObject or a NetworkBehaviour over RPCs or `NetworkVariables`. +There are two convenience wrappers which can be used to send a reference to a `NetworkObject` or a `NetworkBehaviour` over an `RPC` or in a `NetworkVariable`. ## NetworkObjectReference -`NetworkObjectReference` can be used to serialize a reference to a NetworkObject. It can only be used on already spawned `NetworkObjects`. +`NetworkObjectReference` can be used to serialize a reference to a `NetworkObject`. It can only be used on already spawned `NetworkObject`s. Here is an example of using NetworkObject reference to send a target NetworkObject over an RPC: + ```csharp public class Weapon : NetworkBehaviour { @@ -36,6 +40,7 @@ public class Weapon : NetworkBehaviour ### Implicit Operators There are also implicit operators which convert from/to `NetworkObject/GameObject` which can be used to simplify code. For instance the above example can also be written in the following way: + ```csharp public class Weapon : NetworkBehaviour { @@ -51,6 +56,7 @@ public class Weapon : NetworkBehaviour } } ``` + > [!NOTE] > The implicit conversion to NetworkObject / GameObject will result in `Null` if the reference can't be found. @@ -85,6 +91,6 @@ public class Weapon : NetworkBehaviour ## How NetworkObjectReference & NetworkBehaviourReference work -`NetworkObjectReference` and `NetworkBehaviourReference` are convenience wrappers which serialize the id of a NetworkObject when being sent and on the receiving end retrieve the corresponding ` ` with that id. `NetworkBehaviourReference` sends an additional index which is used to find the right NetworkBehaviour on the NetworkObject. +`NetworkObjectReference` and `NetworkBehaviourReference` are convenience wrappers which serialize the id of a `NetworkObject` when being sent and on the receiving end retrieve the corresponding `NetworkObject` with that id. `NetworkBehaviourReference` sends an additional index which is used to find the right `NetworkBehaviour` on the `NetworkObject`. -Both of them are structs implementing the `INetworkSerializable` interface. +Both of them are structs implementing the [`INetworkSerializable`](./inetworkserializable.md) interface. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md index c347f4c13e..5681e5952c 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md @@ -1,6 +1,9 @@ # Arrays and native containers -Netcode for GameObjects has built-in serialization code for arrays of [C# value-type primitives](cprimitives.md), like `int[]`, and [Unity primitive types](unity-primitives.md). Any arrays of types that aren't handled by the built-in serialization code, such as `string[]`, need to be handled using a container class or structure that implements the [`INetworkSerializable`](inetworkserializable.md) interface. +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning how to serialize arrays and native containers. + +Netcode for GameObjects has built-in serialization code for arrays of [C# value-type primitives](cprimitives.md), like `int[]`, and [Unity primitive types](unity-primitives.md). Any arrays of types that aren't handled by the built-in serialization code, such as custom types, need to be handled using a container class or structure that implements the [`INetworkSerializable`](inetworkserializable.md) interface. ## Performance considerations @@ -17,37 +20,6 @@ Using built-in primitive types is fairly straightforward: void HelloServerRpc(int[] scores, Color[] colors) { /* ... */ } ``` -## INetworkSerializable implementation example - -There are many ways to handle sending an array of managed types. The following example is a simple `string` container class that implements `INetworkSerializable` and can be used as an array of "StringContainers": - -```csharp -[Rpc(SendTo.ClientsAndHost)] -void SendMessagesClientRpc(StringContainer[] messages) -{ - foreach (var stringContainer in stringContainers) - { - Debug.Log($"{stringContainer.SomeText}"); - } -} - -public class StringContainer : INetworkSerializable -{ - public string SomeText; - public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter - { - if (serializer.IsWriter) - { - serializer.GetFastBufferWriter().WriteValueSafe(SomeText); - } - else - { - serializer.GetFastBufferReader().ReadValueSafe(out SomeText); - } - } -} -``` - ## Native containers Netcode for GameObjects supports `NativeArray` and `NativeList` native containers with built-in serialization, RPCs, and NetworkVariables. However, you can't nest either of these containers without causing a crash. @@ -77,3 +49,7 @@ To serialize a `NativeList` container, you must: > [!NOTE] > When using `NativeLists` within `INetworkSerializable`, the list `ref` value must be a valid, initialized `NativeList`. > NetworkVariables are similar that the value must be initialized before it can receive updates. For example, `public NetworkVariable> ByteListVar = new NetworkVariable>{Value = new NativeList(Allocator.Persistent)};`. RPCs do this automatically. + +## Generic collections + +For performance reasons, Netcode for GameObjects does not have built-in serialization code for C# generic collections. However, `NetworkVariable` does support [synchronizing generic collection types](../../basics/networkvariable.md#using-collections-with-networkvariables). diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md new file mode 100644 index 0000000000..fb52e42773 --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md @@ -0,0 +1,36 @@ +# Serialization overview + +Netcode for GameObjects uses a default serialization pipeline when using [`RPC`](../../rpc-landing.md)'s, [`NetworkVariable`](../../networkvariables-landing.md)s, or any other Netcode-related tasks that require serialization. The serialization pipeline looks like this: + +`` +Custom types => Built-in types => INetworkSerializable +`` + +When Netcode for GameObjects first receives a type, it checks for any custom types that you have registered for serialization, then it checks if it's a built-in type, such as a Vector3 or a float. These are handled by default. Otherwise, it checks if the type inherits [`INetworkSerializable`](inetworkserializable.md), and if it does, it calls its write methods. + +By default, any type that satisfies the unmanaged generic constraint can be automatically serialized as RPC parameters. This includes all basic types (bool, byte, int, float, enum, for example), as well as any structs that contain only these basic types. + +Serialization and deserialization is done via the structs [`FastBufferWriter` and `FastBufferReader`](fastbufferwriter-fastbufferreader.md). These have methods for serializing individual types and methods for serializing packed numbers, but in particular provide a high-performance method called `WriteValue()/ReadValue()` (for Writers and Readers, respectively) that can extremely quickly write an entire unmanaged struct to a buffer. + +`FastBufferWriter` and `FastBufferReader` also contain the functions `FastBufferWriter.WriteNetworkSerializable()` and `FastBufferReader.ReadNetworkSerializable` for writing and reading values that use the `INetworkSerializable` interface. + +## Built-in serialization + +* [C# primitives](./cprimitives.md) +* [Unity primitives](./unity-primitives.md) +* [Enum types](./enum-types.md) +* [Arrays](./serialization-arrays.md) +* [Collections](../../basics/networkvariable.md#using-collections-with-networkvariables) + +## Custom serialization + +* [INetworkSerializable](./inetworkserializable.md) +* [INetworkSerializeByMemcpy](./inetworkserializebymemcpy.md) +* [Customizing serialization](../custom-serialization.md) +* [Custom NetworkVariable implementations](../../basics/custom-networkvariables.md) + +## Additional resources + +* [NetworkObject serialization](./networkobject-serialization.md) +* [FastBufferWriter and FastBufferReader](../fastbufferwriter-fastbufferreader.md) +* [BufferSerializer](../bufferserializer.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md new file mode 100644 index 0000000000..e4f45a5ead --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md @@ -0,0 +1,18 @@ +# Single player sessions + +Netcode for GameObjects provides a [SinglePlayerTransport](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.13/api/Unity.Netcode.Transports.SinglePlayer.SinglePlayerTransport.html) which derives from [NetworkTransport](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.13/api/Unity.Netcode.NetworkTransport.html). + +This provides the ability to run a hosted session using the single player transport without having to modify your primary netcode script. + +## Adding the single player transport + +- Add the `SinglePlayerTransport` to your NetworkManager. +- You can create a custom `MonoBehaviour` component to handle your connection flow or you can derive from `NetworkManager` and add additional methods/logic to handle starting a single player session or multiplayer session. + - When starting a single player session, prior to starting the NetworkManager as a host (_required_), you will want to assign the `SinglePlayerTransport` to the `NetworkManager.NetworkConfig.NetworkTransport`. + - When starting a multiplayer session, prior to starting the NetworkManager, you will want to assign the `UnityTransport` (_or any other `NetworkTransport` derived class that you might use for multiplayer sessions_) to the `NetworkManager.NetworkConfig.NetworkTransport`. + +## Example script + +Below is an example component script that provides a single method to start a single or multi player session: + +[!code-cs[](../../Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs#SinglePlayerTransportExample)] diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md b/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md index c2c1f97286..de160d26d2 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md @@ -2,6 +2,9 @@ In addition to the standard [`NetworkVariable`s](networkvariable.md) available in Netcode for GameObjects, you can also create custom `NetworkVariable`s for advanced implementations. The `NetworkVariable` and `NetworkList` classes were created as `NetworkVariableBase` class implementation examples. While the `NetworkVariable` class is considered production ready, you might run into scenarios where you have a more advanced implementation in mind. In this case, you can create your own custom implementation. +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand how Netcode for GameObjects handles serialization. + To create your own `NetworkVariableBase`-derived container, you should: 1. Create a class deriving from `NetworkVariableBase`. @@ -16,11 +19,14 @@ To create your own `NetworkVariableBase`-derived container, you should: The way you read and write `NetworkVariable`s changes depending on the type you use. -* Known, non-generic types: Use `FastBufferReader.ReadValue` to read from and `FastBufferWriter.WriteValue` to write to the `NetworkVariable` value. -* Integer types: This type gives you the option to use `BytePacker` and `ByteUnpacker` to compress the `NetworkVariable` value. This process can save bandwidth but adds CPU processing time. +* Known, non-generic types can use [`FastBufferWriter` and `FastBufferReader`](../advanced-topics/fastbufferwriter-fastbufferreader.md) to serialize the `NetworkVariable` value. +* Integer types: This type gives you the option to use [`BytePacker` and `ByteUnpacker`](../advanced-topics/fastbufferwriter-fastbufferreader.md#packing) to compress the `NetworkVariable` value. This process can save bandwidth but adds CPU processing time. * Generic types: Use serializers that Unity generates based on types discovered during a compile-time code generation process. This means you need to tell Unity's code generation algorithm which types to generate serializers for. To tell Unity which types to serialize, use the following methods: * Use `GenerateSerializationForTypeAttribute` to serialize hard-coded types. * Use `GenerateSerializationForGenericParameterAttribute` to serialize generic types. +* Types implementing [`INetworkSerializable`](../advanced-topics/serialization/inetworkserializable.md) can also be serialized using `FastBufferWriter` and `FastBufferReader`. + +Read more about [custom serialization](../advanced-topics/custom-serialization.md) for more approaches to customizing serialization. ### Serialize a hard-coded type @@ -66,175 +72,22 @@ For dynamically-allocated types with a value that isn't `null` (for example, man You can use `AreEqual` to determine if a value is different from the value that `Duplicate` cached. This avoids sending the same value multiple times. You can also use the previous value that `Duplicate` cached to calculate deltas to use in `ReadDelta` and `WriteDelta`. -The type you use must be serializable according to the [support types list above](#supported-types). Each type needs its own serializer instantiated, so this step tells the codegen which types to create serializers for. Unity's code generator assumes that all `NetworkVariable` types exist as fields inside NetworkBehaviour types. This means that Unity only inspects fields inside NetworkBehaviour types to identify the types to create serializers for. +The type you use must be serializable according to the [supported types list](./networkvariable.md#supported-types). Each type needs its own serializer instantiated, so this step tells the codegen which types to create serializers for. Unity's code generator assumes that all `NetworkVariable` types exist as fields inside NetworkBehaviour types. This means that Unity only inspects fields inside NetworkBehaviour types to identify the types to create serializers for. -## Custom NetworkVariable example +> [!NOTE] +> These attributes won't generate delta serialization. If you would like to customize how deltas are sent when a custom value has partially changed, refer to [`UserNetworkVariableSerialization`](../advanced-topics/custom-serialization.md#networkvariable). + +## Custom NetworkVariableBase example This example shows a custom `NetworkVariable` type to help you understand how you might implement such a type. In the current version of Netcode for GameObjects, this example is possible without using a custom `NetworkVariable` type; however, for more complex situations that aren't natively supported, this basic example should help inform you of how to approach the implementation: - ```csharp - /// Using MyCustomNetworkVariable within a NetworkBehaviour - public class TestMyCustomNetworkVariable : NetworkBehaviour - { - public MyCustomNetworkVariable CustomNetworkVariable = new MyCustomNetworkVariable(); - public MyCustomGenericNetworkVariable CustomGenericNetworkVariable = new MyCustomGenericNetworkVariable(); - public override void OnNetworkSpawn() - { - if (IsServer) - { - for (int i = 0; i < 4; i++) - { - var someData = new SomeData(); - someData.SomeFloatData = (float)i; - someData.SomeIntData = i; - someData.SomeListOfValues.Add((ulong)i + 1000000); - someData.SomeListOfValues.Add((ulong)i + 2000000); - someData.SomeListOfValues.Add((ulong)i + 3000000); - CustomNetworkVariable.SomeDataToSynchronize.Add(someData); - CustomNetworkVariable.SetDirty(true); - - CustomGenericNetworkVariable.SomeDataToSynchronize.Add(i); - CustomGenericNetworkVariable.SetDirty(true); - } - } - } - } - - /// Bare minimum example of NetworkVariableBase derived class - [Serializable] - public class MyCustomNetworkVariable : NetworkVariableBase - { - /// Managed list of class instances - public List SomeDataToSynchronize = new List(); - - /// - /// Writes the complete state of the variable to the writer - /// - /// The stream to write the state to - public override void WriteField(FastBufferWriter writer) - { - // Serialize the data we need to synchronize - writer.WriteValueSafe(SomeDataToSynchronize.Count); - foreach (var dataEntry in SomeDataToSynchronize) - { - writer.WriteValueSafe(dataEntry.SomeIntData); - writer.WriteValueSafe(dataEntry.SomeFloatData); - writer.WriteValueSafe(dataEntry.SomeListOfValues.Count); - foreach (var valueItem in dataEntry.SomeListOfValues) - { - writer.WriteValueSafe(valueItem); - } - } - } - - /// - /// Reads the complete state from the reader and applies it - /// - /// The stream to read the state from - public override void ReadField(FastBufferReader reader) - { - // De-Serialize the data being synchronized - var itemsToUpdate = (int)0; - reader.ReadValueSafe(out itemsToUpdate); - SomeDataToSynchronize.Clear(); - for (int i = 0; i < itemsToUpdate; i++) - { - var newEntry = new SomeData(); - reader.ReadValueSafe(out newEntry.SomeIntData); - reader.ReadValueSafe(out newEntry.SomeFloatData); - var itemsCount = (int)0; - var tempValue = (ulong)0; - reader.ReadValueSafe(out itemsCount); - newEntry.SomeListOfValues.Clear(); - for (int j = 0; j < itemsCount; j++) - { - reader.ReadValueSafe(out tempValue); - newEntry.SomeListOfValues.Add(tempValue); - } - SomeDataToSynchronize.Add(newEntry); - } - } - - public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) - { - // Do nothing for this example - } - - public override void WriteDelta(FastBufferWriter writer) - { - // Do nothing for this example - } - } - - /// Bare minimum example of generic NetworkVariableBase derived class - [Serializable] - [GenerateSerializationForGenericParameter(0)] - public class MyCustomGenericNetworkVariable : NetworkVariableBase - { - /// Managed list of class instances - public List SomeDataToSynchronize = new List(); - - /// - /// Writes the complete state of the variable to the writer - /// - /// The stream to write the state to - public override void WriteField(FastBufferWriter writer) - { - // Serialize the data we need to synchronize - writer.WriteValueSafe(SomeDataToSynchronize.Count); - for (var i = 0; i < SomeDataToSynchronize.Count; ++i) - { - var dataEntry = SomeDataToSynchronize[i]; - // NetworkVariableSerialization is used for serializing generic types - NetworkVariableSerialization.Write(writer, ref dataEntry); - } - } - - /// - /// Reads the complete state from the reader and applies it - /// - /// The stream to read the state from - public override void ReadField(FastBufferReader reader) - { - // De-Serialize the data being synchronized - var itemsToUpdate = (int)0; - reader.ReadValueSafe(out itemsToUpdate); - SomeDataToSynchronize.Clear(); - for (int i = 0; i < itemsToUpdate; i++) - { - T newEntry = default; - // NetworkVariableSerialization is used for serializing generic types - NetworkVariableSerialization.Read(reader, ref newEntry); - SomeDataToSynchronize.Add(newEntry); - } - } - - public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) - { - // Do nothing for this example - } - - public override void WriteDelta(FastBufferWriter writer) - { - // Do nothing for this example - } - } - - /// Example managed class used as the item type in the - /// MyCustomNetworkVariable.SomeDataToSynchronize list - [Serializable] - public class SomeData - { - public int SomeIntData = default; - public float SomeFloatData = default; - public List SomeListOfValues = new List(); - } - ``` +[!code-cs[](../../Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs#TestMyCustomNetworkVariable)] While the above example isn't the recommended way to synchronize a list where the number or order of elements in the list often changes, it's an example of how you can define your own rules using `NetworkVariableBase`. You can test the code above by: -- Using the above code with a project that includes Netcode for GameObjects v1.0 (or higher). + +- Using the above code with a project that includes Netcode for GameObjects. - Adding the `TestMyCustomNetworkVariable` component to an in-scene placed NetworkObject. - Creating a stand alone build and running that as a host or server. - Running the same scene within the Editor and connecting as a client. diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md b/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md index df9b3bb506..cfdd037143 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md @@ -552,7 +552,7 @@ public class PlayerState : NetworkBehaviour ## Complex types -Almost all of the examples on this page have been focused around numeric [value types](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-types). Netcode for GameObjects also supports complex types and can support both unmanaged types *and* managed types (although avoiding managed types where possible will improve your game's performance). +Any type that implements the [`INetworkSerializable`](../advanced-topics/serialization/inetworkserializable.md) interface can be used inside a `NetworkVariable`. To synchronize a custom type that is not supported by the default serialization provided by Netcode for GameObjects, implement this interface to allow it to be used inside a `NetworkVariable`. ### Synchronizing complex types example @@ -582,7 +582,7 @@ public class PlayerState : NetworkBehaviour void Awake() { //NetworkList can't be initialized at declaration time like NetworkVariable. It must be initialized in Awake instead. - //If you do initialize at declaration, you will run into Memmory leak errors. + //If you do initialize at declaration, you will run into Memory leak errors. TeamAreaWeaponBoosters = new NetworkList(); } @@ -707,88 +707,12 @@ public struct AreaWeaponBooster : INetworkSerializable, System.IEquatable [!NOTE] -> The `NetworkVariable` and `NetworkList` classes were created as `NetworkVariableBase` class implementation examples. While the `NetworkVariable` class is considered production ready, you might run into scenarios where you have a more advanced implementation in mind. In this case, we encourage you to create your own custom implementation. - -To create your own `NetworkVariableBase` derived container, you should: - -- Create a class deriving from `NetworkVariableBase`. - -- Assure the the following methods are overridden: - - `void WriteField(FastBufferWriter writer)` - - `void ReadField(FastBufferReader reader)` - - `void WriteDelta(FastBufferWriter writer)` - - `void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)` -- Depdending upon your custom `NetworkVariableBase` container, you might look at `NetworkVariable` or `NetworkList` to see how those two examples were implemented. - - - -#### NetworkVariableSerialization<T> - -The way you read and write network variables changes depending on the type you use. - -* Known, non-generic types: Use `FastBufferReader.ReadValue` to read from and `FastBufferWriter.WriteValue` to write to the network variable value. -* Integer types: This type gives you the option to use `BytePacker` and `ByteUnpacker` to compress the network variable value. This process can save bandwidth but adds CPU processing time. -* Generic types: Use serializers that Unity generates based on types discovered during a compile-time code generation process. This means you need to tell Unity's code generation algorithm which types to generate serializers for. To tell Unity which types to serialize, use the following methods: - * Use `GenerateSerializationForTypeAttribute` to serialize hard-coded types. - * Use `GenerateSerializationForGenericParameterAttribute` to serialize generic types. - To learn how to use these methods, refer to [Network variable serialization](#network-variable-serialization). - -##### Tell Unity to serialize a hard-coded type -The following code example uses `GenerateSerializationForTypeAttribute` to generate serialization for a specific hard-coded type: -```csharp -[GenerateSerializationForType(typeof(Foo))] -public class MyNetworkVariableTypeUsingFoo : NetworkVariableBase {} -``` - -You can call a type that you know the name of with the `FastBufferReader` or `FastBufferWriter` methods. These methods don't work for Generic types because the name of the type is unknown. -##### Tell Unity to serialize a generic type -The following code example uses `GenerateSerializationForGenericParameterAttribute` to generate serialization for a specific Generic parameter in your `NetworkVariable` type: -```csharp -[GenerateSerializationForGenericParameter(0)] -public class MyNetworkVariableType : NetworkVariableBase {} -``` - -This attribute accepts an integer that indicates which parameter in the type to generate serialization for. This value is 0-indexed, which means that the first type is 0, the second type is 1, and so on. -The following code example places the attribute more than once on one class to generate serialization for multiple types, in this case,`TFirstType` and `TSecondType: - -```csharp -[GenerateSerializationForGenericParameter(0)] -[GenerateSerializationForGenericParameter(1)] -public class MyNetworkVariableType : NetworkVariableBase {} -``` - - -The `GenerateSerializationForGenericParameterAttribute` and `GenerateSerializationForTypeAttribute` attributes make Unity's code generation create the following methods: - -```csharp -NetworkVariableSerialization.Write(FastBufferWriter writer, ref T value); -NetworkVariableSerialization.Read(FastBufferWriter writer, ref T value); -NetworkVariableSerialization.Duplicate(in T value, ref T duplicatedValue); -NetworkVariableSerialization.AreEqual(in T a, in T b); -``` - -For dynamically allocated types with a value that isn't `null` (for example, managed types and collections like NativeArray and NativeList) call `Read` to read the value in the existing object and write data into it directy (in-place). This avoids more allocations. - -You can use `AreEqual` to determine if a value is different from the value that `Duplicate` cached. This avoids sending the same value multiple times. You can also use the previous value that `Duplicate` cached to calculate deltas to use in `ReadDelta` and `WriteDelta`. - -The type you use must be serializable according to the "Supported Types" list above. Each type needs its own serializer instantiated, so this step tells the codegen which types to create serializers for. - -> [!NOTE] Unity's code generator assumes that all `NetworkVariable` types exist as fields inside NetworkBehaviour types. This means that Unity only inspects fields inside NetworkBehaviour types to identify the types to create serializers for. - - ### Custom NetworkVariable Example - -This example shows a custom `NetworkVariable` type to help you understand how you might implement such a type. In the current version of Netcode for GameObjects, this example is possible without using a custom `NetworkVariable` type; however, for more complex situations that aren't natively supported, this basic example should help inform you of how to approach the implementation: - -Looking at the read and write segments of code within `AreaWeaponBooster.NetworkSerialize`, the nested complex type property `ApplyWeaponBooster` handles its own serialization and de-serialization. The `ApplyWeaponBooster`'s implemented `NetworkSerialize` method serializes and deserializes any `AreaWeaponBooster` type property. This design approach can help reduce code replication while providing a more modular foundation to build even more complex, nested types. - +Further information on customizing the serialization of complex types can be found in [custom serialization](../advanced-topics/custom-serialization.md#networkvariable). ## Strings -While `NetworkVariable` does support managed `INetworkSerializable` types, strings aren't in the list of supported types. This is because strings in C# are immutable types, preventing them from being deserialized in-place, so every update to a `NetworkVariable` would cause a Garbage Collected allocation to create the new string, which may lead to performance problems. +While `NetworkVariable` does support managed `INetworkSerializable` types, strings aren't in the [list of supported types](#supported-types). This is because strings in C# are immutable types, preventing them from being deserialized in-place, so every update to a `NetworkVariable` would cause a Garbage Collected allocation to create the new string, which may lead to performance problems. -While it's technically possible to support strings using custom serialization through `UserNetworkVariableSerialization`, it isn't recommended to do so due to the performance implications that come with it. Instead, we recommend using one of the `Unity.Collections.FixedString` value types. In the below example, we used a `FixedString128Bytes` as the `NetworkVariable` value type. On the server side, it changes the string value each time you press the space bar on the server or host instance. Joining clients will be synchronized with the current value applied on the server side, and each time you hit the space bar on the server side, the client synchronizes with the changed string. +While it's technically possible to support strings using custom serialization through [`UserNetworkVariableSerialization`](../advanced-topics/custom-serialization.md#networkvariable), it isn't recommended to do so due to the performance implications that come with it. Instead, it's recommended to use one of the `Unity.Collections.FixedString` value types. In the below example, a `FixedString128Bytes` is the `NetworkVariable` value type. On the server side, it changes the string value each time you press the space bar on the server or host instance. Joining clients will be synchronized with the current value applied on the server side, and each time you hit the space bar on the server side, the client synchronizes with the changed string. > [!NOTE] > `NetworkVariable` won't serialize the entire 128 bytes each time the `Value` is changed. Only the number of bytes that are actually used to store the string value will be sent, no matter which size of `FixedString` you use. @@ -855,3 +779,9 @@ public class TestFixedString : NetworkBehaviour > [!NOTE] > The above example uses a pre-set list of strings to cycle through for example purposes only. If you have a predefined set of text strings as part of your actual design then you would not want to use a FixedString to handle synchronizing the changes to `m_TextString`. Instead, you would want to use a `uint` for the type `T` where the `uint` was the index of the string message to apply to `m_TextString`. + +### Delta updates + +To save bandwidth, `NetworkVariables` can send delta updates. A delta is a compact description of what changed since the last sync. By default, [collection types](#using-collections-with-networkvariables) all support sending delta updates. This means adding a single item to a large list doesn't need to send the entire list over the network. For complex types, it is often worth considering whether sending deltas will improve bandwidth. Delta serialization can be configured via [`UserNetworkVariableSerialization`](../advanced-topics/custom-serialization.md#serializing-delta-updates). + +When a NetworkObject is spawned or a late-joining client first sees it, every `NetworkVariable` will be serialized in full. This allows game clients to start receiving deltas from a known state. diff --git a/com.unity.netcode.gameobjects/Documentation~/serialization.md b/com.unity.netcode.gameobjects/Documentation~/serialization.md index fb39fad334..4dfd16f484 100644 --- a/com.unity.netcode.gameobjects/Documentation~/serialization.md +++ b/com.unity.netcode.gameobjects/Documentation~/serialization.md @@ -4,11 +4,14 @@ Netcode for GameObjects has built-in serialization support for C# and Unity prim | **Topic** | **Description** | | :------------------------------ | :------------------------------- | +| **[Serialization overview](advanced-topics/serialization/serialization-overview.md)** | An overview of how Netcode for GameObjects handles serialization. | | **[C# primitives](advanced-topics/serialization/cprimitives.md)** | C# primitive types are serialized by built-in serialization code. These types include `bool`, `char`, `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, and `string`. | | **[Unity primitives](advanced-topics/serialization/unity-primitives.md)** | Unity Primitive `Color`, `Color32`, `Vector2`, `Vector3`, `Vector4`, `Quaternion`, `Ray`, `Ray2D` types will be serialized by built-in serialization code. | | **[Enum types](advanced-topics/serialization/enum-types.md)** | A user-defined enum type will be serialized by built-in serialization code (with underlying integer type). | | **[Arrays](advanced-topics/serialization/serialization-arrays.md)** | Netcode for GameObjects has built-in serialization code for arrays of [C# value-type primitives](advanced-topics/serialization/cprimitives.md), like `int[]`, and [Unity primitive types](advanced-topics/serialization/unity-primitives.md). Any arrays of types that aren't handled by the built-in serialization code, such as `string[]`, need to be handled using a container class or structure that implements the [`INetworkSerializable`](advanced-topics/serialization/inetworkserializable.md) interface. | -| **[INetworkSerializable](advanced-topics/serialization/inetworkserializable.md)** | You can use the `INetworkSerializable` interface to define custom serializable types. | +| **[Customize serializable types with INetworkSerializable](advanced-topics/serialization/inetworkserializable.md)** | You can use the `INetworkSerializable` interface to define custom serializable types. | +| **[Serialize unmanaged structs with INetworkSerializeByMemcpy](advanced-topics/serialization/inetworkserializebymemcpy.md)** | You can use the `INetworkSerializeByMemcpy` interface to mark an unmanaged struct type as being trivially serializable. | | **[Custom serialization](advanced-topics/custom-serialization.md)** | Create custom serialization types. | | **[NetworkObject serialization](advanced-topics/serialization/networkobject-serialization.md)** | `GameObjects`, `NetworkObjects` and NetworkBehaviour aren't serializable types so they can't be used in `RPCs` or `NetworkVariables` by default. There are two convenience wrappers which can be used to send a reference to a NetworkObject or a NetworkBehaviour over RPCs or `NetworkVariables`. | -| **[FastBufferWriter and FastBufferReader](advanced-topics/fastbufferwriter-fastbufferreader.md)** | The serialization and deserialization is done via `FastBufferWriter` and `FastBufferReader`. These have methods for serializing individual types and methods for serializing packed numbers, but in particular provide a high-performance method called `WriteValue()/ReadValue()` (for Writers and Readers, respectively) that can extremely quickly write an entire unmanaged struct to a buffer. | \ No newline at end of file +| **[FastBufferWriter and FastBufferReader](advanced-topics/fastbufferwriter-fastbufferreader.md)** | The serialization and deserialization is done via `FastBufferWriter` and `FastBufferReader`. These have methods for serializing individual types and methods for serializing packed numbers, but in particular provide a high-performance method called `WriteValue()/ReadValue()` (for Writers and Readers, respectively) that can extremely quickly write an entire unmanaged struct to a buffer. | +| **[BufferSerializer](advanced-topics/bufferserializer.md)** | A bi-directional serializer primarily used for serializing within [`INetworkSerializable`](serialization/inetworkserializable.md) types. | diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index b1852baf44..3918b03da8 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -2150,9 +2150,10 @@ private bool GetReadMethodForParameter(TypeReference paramType, out MethodRefere { typeMethod = GetFastBufferReaderReadMethod(k_ReadValueInPlaceMethodName, paramType); } - else -#endif + else if (paramType.Resolve().FullName == "Unity.Collections.NativeArray`1") +#else if (paramType.Resolve().FullName == "Unity.Collections.NativeArray`1") +#endif { typeMethod = GetFastBufferReaderReadMethod(k_ReadValueTempMethodName, paramType); } diff --git a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs index 423ff4a65c..f7529de12c 100644 --- a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs +++ b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs @@ -1,3 +1,4 @@ +using Unity.Netcode.Logging; using UnityEditor; using UnityEditor.Build; using UnityEditor.Build.Reporting; @@ -19,8 +20,22 @@ internal class SetInScenePlaced : IProcessSceneWithReport public int callbackOrder => 0; public void OnProcessScene(Scene scene, BuildReport report) { + var log = new ContextualLogger(); + log.AddInfo(scene.name, scene.handle); foreach (var networkObject in FindObjects.FromSceneByType(scene, true)) { + if (networkObject.SceneOrigin.IsValid() && networkObject.SceneOrigin.handle != scene.handle) + { + log.Warning(new Context(LogLevel.Developer, $"{nameof(NetworkObject)}'s SceneOrigin doesn't match current scene being processed! Skipping processing.").AddInfo("SceneOrigin", networkObject.SceneOriginHandle).AddNetworkObject(networkObject)); + continue; + } + + if (networkObject.HasBeenSpawned) + { + log.Error(new Context(LogLevel.Normal, $"Processing {nameof(NetworkObject)} that has already been spawned! This should not be possible. Skipping processing.").AddNetworkObject(networkObject)); + continue; + } + networkObject.InScenePlaced = true; } } diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index abf238c45a..715d15584d 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -195,9 +195,9 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool { return isParented; } - else // If we are no longer a child, then we can remove ourself from this list - if (transform.root == gameObject.transform) + else if (transform.root == gameObject.transform) { + // If we are no longer a child, then we can remove ourself from this list s_LastKnownNetworkManagerParents.Remove(networkManager); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs index ce277533c7..00bd431cbb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs @@ -161,8 +161,18 @@ public void AnticipateMove(Vector3 newPosition) { return; } - transform.position = newPosition; + + if (InLocalSpace) + { + transform.localPosition = newPosition; + } + else + { + transform.position = newPosition; + } + m_AnticipatedTransform.Position = newPosition; + if (CanCommitToTransform) { m_AuthoritativeTransform.Position = newPosition; @@ -187,7 +197,15 @@ public void AnticipateRotate(Quaternion newRotation) { return; } - transform.rotation = newRotation; + + if (InLocalSpace) + { + transform.localRotation = newRotation; + } + else + { + transform.rotation = newRotation; + } m_AnticipatedTransform.Rotation = newRotation; if (CanCommitToTransform) { @@ -240,7 +258,14 @@ public void AnticipateState(TransformState newState) return; } var transform_ = transform; - transform_.SetPositionAndRotation(newState.Position, newState.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(newState.Position, newState.Rotation); + } + else + { + transform_.SetPositionAndRotation(newState.Position, newState.Rotation); + } transform_.localScale = newState.Scale; m_AnticipatedTransform = newState; if (CanCommitToTransform) @@ -277,7 +302,17 @@ private void ProcessSmoothing() m_PreviousAnticipatedTransform = m_AnticipatedTransform; if (!CanCommitToTransform) { - transform_.SetPositionAndRotation(m_AnticipatedTransform.Position, m_AnticipatedTransform.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(m_AnticipatedTransform.Position, + m_AnticipatedTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(m_AnticipatedTransform.Position, + m_AnticipatedTransform.Rotation); + } + transform_.localScale = m_AnticipatedTransform.Scale; } } @@ -320,12 +355,25 @@ public void SetupForRender() if (Transform.CanCommitToTransform) { var transform_ = Transform.transform; - Transform.m_AuthoritativeTransform = new TransformState + if (Transform.InLocalSpace) { - Position = transform_.position, - Rotation = transform_.rotation, - Scale = transform_.localScale - }; + Transform.m_AuthoritativeTransform = new TransformState + { + Position = transform_.localPosition, + Rotation = transform_.localRotation, + Scale = transform_.localScale + }; + } + else + { + Transform.m_AuthoritativeTransform = new TransformState + { + Position = transform_.position, + Rotation = transform_.rotation, + Scale = transform_.localScale + }; + } + if (Transform.m_CurrentSmoothTime >= Transform.m_SmoothDuration) { // If we've had a call to Smooth() we'll continue interpolating. @@ -334,7 +382,17 @@ public void SetupForRender() Transform.m_AnticipatedTransform = Transform.m_AuthoritativeTransform; } - transform_.SetPositionAndRotation(Transform.m_AnticipatedTransform.Position, Transform.m_AnticipatedTransform.Rotation); + if (Transform.InLocalSpace) + { + transform_.SetLocalPositionAndRotation(Transform.m_AnticipatedTransform.Position, + Transform.m_AnticipatedTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(Transform.m_AnticipatedTransform.Position, + Transform.m_AnticipatedTransform.Rotation); + } + transform_.localScale = Transform.m_AnticipatedTransform.Scale; } } @@ -344,7 +402,17 @@ public void SetupForUpdate() if (Transform.CanCommitToTransform) { var transform_ = Transform.transform; - transform_.SetPositionAndRotation(Transform.m_AuthoritativeTransform.Position, Transform.m_AuthoritativeTransform.Rotation); + if (Transform.InLocalSpace) + { + transform_.SetLocalPositionAndRotation(Transform.m_AuthoritativeTransform.Position, + Transform.m_AuthoritativeTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(Transform.m_AuthoritativeTransform.Position, + Transform.m_AuthoritativeTransform.Rotation); + } + transform_.localScale = Transform.m_AuthoritativeTransform.Scale; } } @@ -367,12 +435,25 @@ public void ResetAnticipation() private void ResetAnticipatedState() { var transform_ = transform; - m_AuthoritativeTransform = new TransformState + if (InLocalSpace) + { + m_AuthoritativeTransform = new TransformState + { + Position = transform_.localPosition, + Rotation = transform_.localRotation, + Scale = transform_.localScale + }; + } + else { - Position = transform_.position, - Rotation = transform_.rotation, - Scale = transform_.localScale - }; + m_AuthoritativeTransform = new TransformState + { + Position = transform_.position, + Rotation = transform_.rotation, + Scale = transform_.localScale + }; + } + m_AnticipatedTransform = m_AuthoritativeTransform; m_PreviousAnticipatedTransform = m_AnticipatedTransform; @@ -439,7 +520,7 @@ public override void OnNetworkSpawn() return; } m_OutstandingAuthorityChange = true; - ApplyAuthoritativeState(); + //ApplyAuthoritativeState(); ResetAnticipatedState(); m_AnticipatedObject = new AnticipatedObject { Transform = this }; @@ -491,7 +572,15 @@ public void Smooth(TransformState from, TransformState to, float durationSeconds { m_AnticipatedTransform = to; m_PreviousAnticipatedTransform = m_AnticipatedTransform; - transform_.SetPositionAndRotation(to.Position, to.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(to.Position, to.Rotation); + } + else + { + transform_.SetPositionAndRotation(to.Position, to.Rotation); + } + transform_.localScale = to.Scale; m_SmoothDuration = 0; m_CurrentSmoothTime = 0; @@ -502,7 +591,15 @@ public void Smooth(TransformState from, TransformState to, float durationSeconds if (!CanCommitToTransform) { - transform_.SetPositionAndRotation(from.Position, from.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(from.Position, from.Rotation); + } + else + { + transform_.SetPositionAndRotation(from.Position, from.Rotation); + } + transform_.localScale = from.Scale; } @@ -543,14 +640,30 @@ protected override void OnTransformUpdated() var previousAnticipatedTransform = m_AnticipatedTransform; // Update authority state to catch any possible interpolation data - m_AuthoritativeTransform.Position = transform_.position; - m_AuthoritativeTransform.Rotation = transform_.rotation; - m_AuthoritativeTransform.Scale = transform_.localScale; + if (InLocalSpace) + { + m_AuthoritativeTransform.Position = transform_.localPosition; + m_AuthoritativeTransform.Rotation = transform_.localRotation; + m_AuthoritativeTransform.Scale = transform_.localScale; + } + else + { + m_AuthoritativeTransform.Position = transform_.position; + m_AuthoritativeTransform.Rotation = transform_.rotation; + m_AuthoritativeTransform.Scale = transform_.localScale; + } if (!m_OutstandingAuthorityChange) { // Keep the anticipated value unchanged, we have no updates from the server at all. - transform_.SetPositionAndRotation(previousAnticipatedTransform.Position, previousAnticipatedTransform.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(previousAnticipatedTransform.Position, previousAnticipatedTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(previousAnticipatedTransform.Position, previousAnticipatedTransform.Rotation); + } transform_.localScale = previousAnticipatedTransform.Scale; return; } @@ -558,7 +671,17 @@ protected override void OnTransformUpdated() if (StaleDataHandling == StaleDataHandling.Ignore && m_LastAnticipaionCounter > m_LastAuthorityUpdateCounter) { // Keep the anticipated value unchanged because it is more recent than the authoritative one. - transform_.SetPositionAndRotation(previousAnticipatedTransform.Position, previousAnticipatedTransform.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(previousAnticipatedTransform.Position, + previousAnticipatedTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(previousAnticipatedTransform.Position, + previousAnticipatedTransform.Rotation); + } + transform_.localScale = previousAnticipatedTransform.Scale; return; } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 24eb580402..8ee288b02f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -467,8 +467,8 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime InterpolateState.CurrentValue = InterpolateState.NextValue; } } - else // If the target is reached and we have no more state updates, we want to check to see if we need to reset. - if (m_BufferQueue.Count == 0) + // If the target is reached and we have no more state updates, we want to check to see if we need to reset. + else if (m_BufferQueue.Count == 0) { // When the delta between the time sent and the current tick latency time-window is greater than the max delta time // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), @@ -593,8 +593,8 @@ public T Update(float deltaTime, double renderTime, double serverTime) // Determine if we have reached our target InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision()); } - else // If the target is reached and we have no more state updates, we want to check to see if we need to reset. - if (InterpolateState.TargetReached && m_BufferQueue.Count == 0) + // If the target is reached and we have no more state updates, we want to check to see if we need to reset. + else if (InterpolateState.TargetReached && m_BufferQueue.Count == 0) { // When the delta between the time sent and the current tick latency time-window is greater than the max delta time // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index 246d6b8484..c999c26e9f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -1180,8 +1180,7 @@ internal void CheckForAnimatorChanges() { SendAnimStateRpc(m_AnimationMessage); } - else - if (!IsServer && IsOwner) + else if (!IsServer && IsOwner) { SendServerAnimStateRpc(m_AnimationMessage); } @@ -1346,8 +1345,7 @@ private unsafe void WriteParameters(ref FastBufferWriter writer) BytePacker.WriteValuePacked(writer, (uint)valueInt); } } - else - if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool) + else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool) { var valueBool = m_Animator.GetBool(hash); fixed (void* value = cacheValue.Value) @@ -1379,6 +1377,13 @@ private unsafe void ReadParameters(FastBufferReader reader) while (totalParametersRead < totalParametersToRead) { ByteUnpacker.ReadValuePacked(reader, out uint parameterIndex); + + // Do bounds check prior to getting the element as a reference at that index. + if (parameterIndex >= m_CachedAnimatorParameters.Length) + { + NetworkManager.Log.ErrorServer(new Logging.Context(LogLevel.Error, $"[{nameof(NetworkAnimator)}][{name}] Invalid index of {parameterIndex} was received when there are only {m_CachedAnimatorParameters.Length} parameters. Ignoring the remainger of this {nameof(ParametersUpdateMessage)}!")); + return; + } ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef(m_CachedAnimatorParameters.GetUnsafePtr(), (int)parameterIndex); var hash = cacheValue.Hash; if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index ff0636c5b4..bb6c6b54d8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using UnityEngine; + namespace Unity.Netcode.Components { /// @@ -604,7 +605,7 @@ private void InternalMoveRotation2D(Quaternion rotation) { var quaternion = Quaternion.identity; var angles = quaternion.eulerAngles; - angles.z = m_InternalRigidbody2D.rotation; + angles.z = rotation.z; quaternion.eulerAngles = angles; m_InternalRigidbody2D.MoveRotation(quaternion); } @@ -878,6 +879,28 @@ public void SetIsKinematic(bool isKinematic) PostSetIsKinematic(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool HasInterpolationTypeSet(InterpolationTypes interpolationType) + { +#if COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D + if (m_IsRigidbody2D) + { + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Extrapolate : m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Interpolate; + } + else + { + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate : m_InternalRigidbody.interpolation == RigidbodyInterpolation.Interpolate; + } +#endif +#if COM_UNITY_MODULES_PHYSICS && !COM_UNITY_MODULES_PHYSICS2D + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate : m_InternalRigidbody.interpolation == RigidbodyInterpolation.Interpolate; +#endif +#if !COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Extrapolate : m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Interpolate; +#endif + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void PostSetIsKinematic() { @@ -886,26 +909,29 @@ private void PostSetIsKinematic() { return; } + if (UseRigidBodyForMotion) { - // Only if the NetworkTransform is set to interpolate do we need to check for extrapolation - if (NetworkTransform.Interpolate && m_OriginalInterpolation == InterpolationTypes.Extrapolate) + // Exit early if the original interpolation type is not set to extrapolate or NetworkTransform interpolate is disabled + if (m_OriginalInterpolation != InterpolationTypes.Extrapolate || !NetworkTransform.Interpolate) { - if (IsKinematic()) - { - // If not already set to interpolate then set the Rigidbody to interpolate - if (m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate) - { - // Sleep until the next fixed update when switching from extrapolation to interpolation - SleepRigidbody(); - SetInterpolation(InterpolationTypes.Interpolate); - } - } - else - { - // Switch it back to the original interpolation if non-kinematic (doesn't require sleep). - SetInterpolation(m_OriginalInterpolation); - } + return; + } + + // Otherwise, if this is the active physics object + if (!IsKinematic()) + { + // switch it back to the original interpolation and exit early + SetInterpolation(m_OriginalInterpolation); + return; + } + + // If the Rigidbody or Rigidbody2D is currently configured to extrapolate + if (HasInterpolationTypeSet(InterpolationTypes.Extrapolate)) + { + // sleep until the next fixed update when switching from extrapolation to interpolation + SleepRigidbody(); + SetInterpolation(InterpolationTypes.Interpolate); } } else diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 34ff4aba1d..de4e86999d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -2526,8 +2526,8 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is } } } - else // Just apply the full local scale when synchronizing - if (SynchronizeScale) + // Just apply the full local scale when synchronizing + else if (SynchronizeScale) { var localScale = CachedTransform.localScale; if (!UseHalfFloatPrecision) @@ -3499,8 +3499,8 @@ private void NonAuthorityFinalizeSynchronization() child.InternalInitialization(); } } - else // Otherwise, just run through standard synchronization of this instance - if (!CanCommitToTransform) + // Otherwise, just run through standard synchronization of this instance + else if (!CanCommitToTransform) { ApplySynchronization(); InternalInitialization(); diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs index 25d75adf26..a873404b3c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs @@ -178,6 +178,21 @@ public bool Validate(int index = -1) // Mark this network prefab as having to be registered via the unified API HasGhost = networkObject.HasGhost; #endif + if (networkObject.InScenePlaced) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(this)} InScenePlaced {nameof(NetworkObject)} is being registered as a Prefab. This can cause issues!"); + } + } + + if (networkObject.IsSpawned) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(this)} Currently spawned {nameof(NetworkObject)} is being registered as a Prefab. This can cause issues!"); + } + } return true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 5a8724374c..3ff131131f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -967,8 +967,7 @@ internal void ProcessClientsToDisconnect() { return (true, playerPrefabHash.Value); } - else - if (NetworkManager.NetworkConfig.PlayerPrefab != null) + else if (NetworkManager.NetworkConfig.PlayerPrefab != null) { var networkObject = NetworkManager.NetworkConfig.PlayerPrefab.GetComponent(); if (networkObject != null) @@ -1218,7 +1217,7 @@ internal void ApprovedPlayerSpawn(ulong clientId, uint playerPrefabHash) var message = new CreateObjectMessage { - ObjectInfo = ConnectedClients[clientId].PlayerObject.Serialize(clientPair.Key), + ObjectInfo = ConnectedClients[clientId].PlayerObject.SerializeSpawnedObject(clientPair.Key), IncludesSerializedObject = true, }; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index f9a1eed8a4..7c02b6a5d4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using Unity.Netcode.Components; +using Unity.Netcode.Logging; using Unity.Netcode.Runtime; #if UNIFIED_NETCODE using Unity.NetCode; @@ -105,6 +107,9 @@ public uint PrefabIdHash /// public NetworkObject CurrentParent { get; private set; } + private int m_SpawnCount; + internal bool HasBeenSpawned => m_SpawnCount > 0; + #if UNITY_EDITOR private const string k_GlobalIdTemplate = "GlobalObjectId_V1-{0}-{1}-{2}-{3}"; @@ -128,9 +133,6 @@ public uint PrefabIdHash // The InContext or InIsolation edit mode network prefab scene instance of the prefab asset (s_PrefabAsset). private static NetworkObject s_PrefabInstance; - private static bool s_DebugPrefabIdGeneration; - - [ContextMenu("Refresh In-Scene Prefab Instances")] internal void RefreshAllPrefabInstances() { @@ -331,7 +333,7 @@ internal void OnValidate() /// private void CheckForInScenePlaced() { - if (gameObject.scene.IsValid() && gameObject.scene.isLoaded && gameObject.scene.buildIndex >= 0) + if (gameObject.scene.IsValid() && gameObject.scene.buildIndex >= 0) { if (PrefabUtility.IsPartOfAnyPrefab(this)) { @@ -351,6 +353,9 @@ private void CheckForInScenePlaced() SetSceneObjectStatus(true); #pragma warning restore CS0618 // Type or member is obsolete + // We go ahead and set this for "typical in-scene placed" usage patterns so this is serialized + InScenePlaced = true; + // Default scene migration synchronization to false for in-scene placed NetworkObjects SceneMigrationSynchronization = false; } @@ -482,7 +487,7 @@ public void DeferDespawn(int tickOffset, bool destroy = true) return; } - if (!HasAuthority) + if (!m_HasAuthority) { if (NetworkManagerOwner.LogLevel <= LogLevel.Error) { @@ -699,7 +704,7 @@ public bool SetOwnershipLock(bool lockOwnership = true) } // If we don't have authority exit early - if (!HasAuthority) + if (!m_HasAuthority) { if (NetworkManager.LogLevel <= LogLevel.Error) { @@ -975,7 +980,7 @@ internal void OwnershipRequest(ulong clientRequestingOwnership) // This action is always authorized as long as the client still has authority. // We need to pass in that this is a request approval ownership change. - NetworkManagerOwner.SpawnManager.ChangeOwnership(this, clientRequestingOwnership, HasAuthority, true); + NetworkManagerOwner.SpawnManager.ChangeOwnership(this, clientRequestingOwnership, m_HasAuthority, true); } else { @@ -1221,14 +1226,9 @@ public bool HasOwnershipStatus(OwnershipStatus status) /// /// When in client-server mode, authority should is not considered the same as ownership. /// - public bool HasAuthority => InternalHasAuthority(); + public bool HasAuthority => IsSpawned ? m_HasAuthority : !NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool InternalHasAuthority() - { - var networkManager = NetworkManager; - return networkManager.DistributedAuthorityMode ? OwnerClientId == networkManager.LocalClientId : networkManager.IsServer; - } + private bool m_HasAuthority; /// /// The NetworkManager that owns this NetworkObject. @@ -1298,15 +1298,35 @@ private bool InternalHasAuthority() /// /// Gets if the object is a SceneObject. /// + /// + /// This method is marked for deprecation.
+ /// Use instead. + ///
[Obsolete("Use InScenePlaced instead")] public bool? IsSceneObject { get; internal set; } + /// - /// True if this object is placed in a scene; false otherwise. + /// The serialized value. /// [field: HideInInspector] [field: SerializeField] - public bool InScenePlaced { get; internal set; } + private bool m_InScenePlaced; + + /// + /// True if this object is placed in a scene; false otherwise. + /// + public bool InScenePlaced + { + get + { + return m_InScenePlaced; + } + internal set + { + m_InScenePlaced = value; + } + } /// /// Sets whether this NetworkObject was instantiated as part of a scene @@ -1514,19 +1534,20 @@ public bool IsNetworkVisibleTo(ulong clientId) /// internal Scene SceneOrigin { - get - { - return m_SceneOrigin; - } + get => m_SceneOrigin; set { + if (SceneOriginHandle.IsEmpty() && value.IsValid() && value.isLoaded) + { + SceneOriginHandle = value.handle; + } + // The scene origin should only be set once. // Once set, it should never change. - if (SceneOriginHandle.IsEmpty() && value.IsValid() && value.isLoaded) + if (!m_SceneOrigin.IsValid()) { m_SceneOrigin = value; - SceneOriginHandle = value.handle; } } } @@ -1537,13 +1558,8 @@ internal Scene SceneOrigin ///
internal NetworkSceneHandle GetSceneOriginHandle() { - if (SceneOriginHandle.IsEmpty() && IsSpawned && InScenePlaced) - { - if (NetworkManager.LogLevel <= LogLevel.Error) - { - NetworkLog.LogErrorServer($"{nameof(GetSceneOriginHandle)} called when {nameof(SceneOriginHandle)} is still zero but the {nameof(NetworkObject)} is already spawned!"); - } - } + NetworkLog.InternalAssert(!(IsSpawned && InScenePlaced && SceneOriginHandle.IsEmpty()), $"Spawned in scene placed NetworkObject {name} should always have a valid SceneOriginHandle"); + return !SceneOriginHandle.IsEmpty() ? SceneOriginHandle : gameObject.scene.handle; } @@ -1572,7 +1588,7 @@ public void NetworkShow(ulong clientId) return; } - if (!HasAuthority) + if (!m_HasAuthority) { if (NetworkManagerOwner.DistributedAuthorityMode) { @@ -1667,7 +1683,7 @@ public void NetworkHide(ulong clientId) return; } - if (!HasAuthority) + if (!m_HasAuthority) { if (NetworkManagerOwner.DistributedAuthorityMode) { @@ -1833,7 +1849,7 @@ private void OnDestroy() { // An authorized destroy is when done by the authority instance or done due to a scene event and the NetworkObject // was marked as destroy pending scene event (which means the destroy with scene property was set). - var isAuthorityDestroy = HasAuthority || NetworkManager.DAHost || DestroyPendingSceneEvent; + var isAuthorityDestroy = m_HasAuthority || NetworkManager.DAHost || DestroyPendingSceneEvent; // If the NetworkObject's GameObject is still valid and the scene is still valid and loaded, then we are still valid var isStillValid = gameObject != null && gameObject.scene.IsValid() && gameObject.scene.isLoaded; @@ -1867,7 +1883,7 @@ private void OnDestroy() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject) + private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject) { if (NetworkManagerOwner == null) { @@ -1938,7 +1954,28 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla } } - if (!NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), InScenePlaced, playerObject, ownerClientId, destroyWithScene)) + + // Calculate the legacy IsSceneObject value as the public field is obsolete with warning + // We can't break the public behavior of the field. +#pragma warning disable CS0618 // Type or member is obsolete + var legacyIsSceneObject = IsSceneObject.HasValue && IsSceneObject.Value; +#pragma warning restore CS0618 // Type or member is obsolete + + // If SpawnInternal is being called on an object that is marked as InScenePlaced, + // The scene object was never automatically spawned when the scene was loaded. + // Count this object as a dynamically spawned object. + // TODO-[MTT-15388]: Actually support disabled/not spawned InScenePlaced NetworkObjects + if (InScenePlaced && !HasBeenSpawned) + { + if (NetworkManagerOwner.NetworkConfig.EnableSceneManagement && NetworkManagerOwner.LogLevel <= LogLevel.Developer) + { + Debug.LogWarning($"[{name}][SceneOrigin={SceneOriginHandle}] Dynamically spawning InScenePlaced network object. This can cause issues!", this); + } + + InScenePlaced = false; + } + + if (!NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), legacyIsSceneObject, playerObject, ownerClientId, destroyWithScene)) { if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) { @@ -2078,8 +2115,7 @@ public NetworkObject InstantiateAndSpawn(NetworkManager networkManager, ulong ow /// Should the object be destroyed when the scene is changed public void Spawn(bool destroyWithScene = false) { - var networkManager = NetworkManager; - var clientId = networkManager.DistributedAuthorityMode ? networkManager.LocalClientId : NetworkManager.ServerClientId; + var clientId = NetworkManager.DistributedAuthorityMode ? NetworkManager.LocalClientId : NetworkManager.ServerClientId; SpawnInternal(destroyWithScene, clientId, false); } @@ -2137,10 +2173,72 @@ public void Despawn(bool destroy = true) NetworkManagerOwner.SpawnManager.DespawnObject(this, destroy); } + internal void SetupOnSpawn(ulong networkId, bool isPlayerObject, ulong ownerClientId, bool destroyWithScene) + { + NetworkObjectId = networkId; + IsPlayerObject = isPlayerObject; + OwnerClientId = ownerClientId; + // When spawned, previous owner is always the first assigned owner + PreviousOwnerId = ownerClientId; + m_HasAuthority = NetworkManagerOwner.DistributedAuthorityMode ? OwnerClientId == NetworkManagerOwner.LocalClientId : NetworkManagerOwner.IsServer; + m_SpawnCount++; + IsSpawned = true; + + // If this is the player, and the client is the owner, then lock ownership by default + if (NetworkManagerOwner.DistributedAuthorityMode && NetworkManagerOwner.LocalClientId == ownerClientId && isPlayerObject) + { + AddOwnershipExtended(OwnershipStatusExtended.Locked); + } + + if (IsSpawnAuthority) + { + SetupObservers(); + } + + /* + * Setup scene related settings + */ + DestroyWithScene = InScenePlaced || destroyWithScene; + if (InScenePlaced) + { + // Always check to make sure our scene of origin is properly set for in-scene placed NetworkObjects + // Note: Always check SceneOriginHandle directly at this specific location. + if (SceneOriginHandle.IsEmpty()) + { + SceneOrigin = gameObject.scene; + } + + // If we are an in-scene placed NetworkObject and our InScenePlacedSourceGlobalObjectIdHash is set + // then assign this to the PrefabGlobalObjectIdHash + if (InScenePlacedSourceGlobalObjectIdHash != 0) + { + PrefabGlobalObjectIdHash = InScenePlacedSourceGlobalObjectIdHash; + } + } + else if (ActiveSceneSynchronization) + { + // Just in case it is a recycled NetworkObject, unsubscribe first + SceneManager.activeSceneChanged -= CurrentlyActiveSceneChanged; + SceneManager.activeSceneChanged += CurrentlyActiveSceneChanged; + } + } + + /// + /// Resets this NetworkObject at the end of a session. + /// Ensures scene objects are ready to be reused + /// + internal void ResetOnShutdown() + { + NetworkLog.InternalAssert(NetworkManager.ShutdownInProgress, "This method should only be called while the NetworkManager is shutting down"); + m_SpawnCount = 0; + ResetOnDespawn(); + } + internal void ResetOnDespawn() { // Always clear out the observers list when despawned Observers.Clear(); + m_HasAuthority = false; IsSpawnAuthority = false; IsSpawned = false; DeferredDespawnTick = 0; @@ -2148,6 +2246,62 @@ internal void ResetOnDespawn() RemoveOwnershipExtended(OwnershipStatusExtended.Locked | OwnershipStatusExtended.Requested); } + internal void SetupObservers() + { + NetworkLog.InternalAssert(IsSpawnAuthority, "This function should only be called on the authority."); + + if (!SpawnWithObservers) + { + if (NetworkManagerOwner.DistributedAuthorityMode) + { + // Always add the owner/authority in DA mode even if SpawnWithObservers is false + // (authority should not take into consideration networkObject.CheckObjectVisibility when SpawnWithObservers is false) + AddObserver(OwnerClientId); + } + + return; + } + + // If running as a server only, then make sure to always add the server's client identifier + if (NetworkManagerOwner.IsServer && !NetworkManagerOwner.IsHost) + { + AddObserver(NetworkManager.ServerClientId); + } + + // If SpawnWithObservers is set, + // then add all connected clients as observers + foreach (var clientId in NetworkManagerOwner.ConnectedClientsIds) + { + // If CheckObjectVisibility has a callback, then allow that method determine who the observers are. + if (CheckObjectVisibility != null && !CheckObjectVisibility(clientId)) + { + continue; + } + AddObserver(clientId); + } + + // Intentionally checking as opposed to just assigning in order to generate notification. + if (!Observers.Contains(OwnerClientId)) + { + // The owner only needs to always be included in DA mode. + if (NetworkManagerOwner.DistributedAuthorityMode) + { + if (NetworkManager.LogLevel <= LogLevel.Error) + { + NetworkLog.LogError($"Client-{OwnerClientId} is the owner of {name} but is not an observer! Adding owner as an observer!"); + } + AddObserver(OwnerClientId); + } + else + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"Client-{OwnerClientId} is the owner of {name} but is not an observer! This may cause issues"); + } + } + } + } + /// /// Removes all ownership of an object from any client. Can only be called from server /// @@ -2178,7 +2332,7 @@ public void ChangeOwnership(ulong newOwnerClientId) } return; } - NetworkManagerOwner.SpawnManager.ChangeOwnership(this, newOwnerClientId, HasAuthority); + NetworkManagerOwner.SpawnManager.ChangeOwnership(this, newOwnerClientId, m_HasAuthority); } /// @@ -2187,20 +2341,18 @@ public void ChangeOwnership(ulong newOwnerClientId) /// internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulong newOwnerClientId) { - if (!IsSpawned) - { - if (NetworkManager.LogLevel <= LogLevel.Error) - { - NetworkLog.LogErrorServer($"[{name}][Attempted behavior invoke on ownership changed before {nameof(NetworkObject)} was spawned]"); - } - return; - } + NetworkLog.InternalAssert(IsSpawned, "[{name}][Attempted behavior invoke on ownership changed before {nameof(NetworkObject)} was spawned]"); var distributedAuthorityMode = NetworkManagerOwner.DistributedAuthorityMode; var isServer = NetworkManagerOwner.IsServer; var isPreviousOwner = originalOwnerClientId == NetworkManagerOwner.LocalClientId; var isNewOwner = newOwnerClientId == NetworkManagerOwner.LocalClientId; + if (distributedAuthorityMode) + { + m_HasAuthority = isNewOwner; + } + if (distributedAuthorityMode || isPreviousOwner) { NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, originalOwnerClientId, true); @@ -2413,7 +2565,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) // DANGO-TODO: Do we want to worry about ownership permissions here? // It wouldn't make sense to not allow parenting, but keeping this note here as a reminder. - var isAuthority = HasAuthority || (AllowOwnerToParent && IsOwner); + var isAuthority = m_HasAuthority || (AllowOwnerToParent && IsOwner); // If we don't have authority and we are not shutting down, then don't allow any parenting. // If we are shutting down and don't have authority then allow it. @@ -2488,7 +2640,7 @@ private void OnTransformParentChanged() // With distributed authority, we need to track "valid authoritative" parenting changes. // So, either the authority or AuthorityAppliedParenting is considered a "valid parenting change". - var isParentingAuthority = HasAuthority || AuthorityAppliedParenting || (AllowOwnerToParent && IsOwner); + var isParentingAuthority = m_HasAuthority || AuthorityAppliedParenting || (AllowOwnerToParent && IsOwner); // If we are spawned and don't have authority; reset the parent back to the cached parent and exit if (!isParentingAuthority) { @@ -2642,8 +2794,8 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa m_CachedWorldPositionStays = false; return true; } - else // If the parent still isn't spawned add this to the orphaned children and return false - if (!parentNetworkObject.IsSpawned) + // If the parent still isn't spawned add this to the orphaned children and return false + else if (!parentNetworkObject.IsSpawned) { OrphanChildren.Add(this); return false; @@ -2742,8 +2894,6 @@ internal void InvokeBehaviourNetworkPreSpawn() internal void InvokeBehaviourNetworkSpawn() { - NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); - // Always invoke all InternalOnNetworkSpawn methods on each child NetworkBehaviour // ** before ** invoking OnNetworkSpawn. // This assures all NetworkVariables and RPC related tables have been initialized @@ -3449,7 +3599,12 @@ internal void SynchronizeNetworkBehaviours(ref BufferSerializer serializer } } - internal SerializedObject Serialize(ulong targetClientId = NetworkManager.ServerClientId, bool syncObservers = false) + /// + /// Creates a on an authority client. + /// Used to synchronize state to a non-authority client. + /// + /// This function is the authority mirror of + internal SerializedObject SerializeSpawnedObject(ulong targetClientId = NetworkManager.ServerClientId, bool syncObservers = false) { var obj = new SerializedObject { @@ -3527,25 +3682,23 @@ internal SerializedObject Serialize(ulong targetClientId = NetworkManager.Server } /// - /// Used to deserialize a serialized which occurs - /// when the client is approved or during a scene transition + /// Does a non-authority local spawn of a given . + /// This occurs when the client is approved, a new object is spawned by an authority, or during a scene transition. /// - /// Deserialized scene object data - /// FastBufferReader for the NetworkVariable data - /// NetworkManager instance + /// This function is the non-authority mirror of + /// Deserialized data received from the authority for this + /// FastBufferReader for any additional data sent with this object on spawn. + /// NetworkManager instance. /// will be true if invoked by CreateObjectMessage /// The deserialized NetworkObject or null if deserialization failed - internal static NetworkObject Deserialize(in SerializedObject serializedObject, FastBufferReader reader, NetworkManager networkManager, bool invokedByMessage = false) + [return: MaybeNull] + internal static NetworkObject DeserializeAndSpawnObject(in SerializedObject serializedObject, FastBufferReader reader, NetworkManager networkManager, bool invokedByMessage = false) { var endOfSynchronizationData = reader.Position + serializedObject.SynchronizationDataSize; if (serializedObject.NetworkObjectId == default) { - if (networkManager.LogLevel <= LogLevel.Error) - { - NetworkLog.LogErrorServer($"[{nameof(GlobalObjectIdHash)}={serializedObject.Hash}] Received spawn request with invalid {nameof(NetworkObjectId)} {serializedObject.NetworkObjectId}. This should not happen!"); - } - + NetworkLog.LogErrorServer(new Context(LogLevel.Error, $"Received spawn request with invalid {nameof(NetworkObjectId)}. This should not happen!").AddInfo(nameof(NetworkObjectId), serializedObject.NetworkObjectId).AddInfo(nameof(GlobalObjectIdHash), serializedObject.Hash)); reader.Seek(endOfSynchronizationData); return null; } @@ -3562,7 +3715,8 @@ internal static NetworkObject Deserialize(in SerializedObject serializedObject, { if (networkManager.LogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning($"[{networkObject.name}][Deserialize][{nameof(NetworkBehaviour)}Synchronization][Size mismatch] Expected: {endOfSynchronizationData} Currently At: {reader.Position}!"); + var networkObjectName = networkObject != null ? networkObject.name : "null"; + NetworkLog.LogWarning($"[{networkObjectName}][Deserialize][{nameof(NetworkBehaviour)}Synchronization][Size mismatch] Expected: {endOfSynchronizationData} Currently At: {reader.Position}!"); } reader.Seek(endOfSynchronizationData); } @@ -3636,29 +3790,13 @@ internal static NetworkObject Deserialize(in SerializedObject serializedObject, return networkObject; } - /// - /// Subscribes to changes in the currently active scene - /// - /// - /// Only for dynamically spawned NetworkObjects - /// - internal void SubscribeToActiveSceneForSynch() - { - if (ActiveSceneSynchronization) - { - if (!InScenePlaced) - { - // Just in case it is a recycled NetworkObject, unsubscribe first - SceneManager.activeSceneChanged -= CurrentlyActiveSceneChanged; - SceneManager.activeSceneChanged += CurrentlyActiveSceneChanged; - } - } - } - /// /// If AutoSynchActiveScene is enabled, then this is the callback that handles updating /// a NetworkObject's scene information. /// + /// + /// Should only be used for dynamically spawned NetworkObjects + /// private void CurrentlyActiveSceneChanged(Scene current, Scene next) { // Early exit if the NetworkObject is not spawned, is an in-scene placed NetworkObject, @@ -3692,20 +3830,20 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) return; } + // Don't create notification if there is a scene event in progress. if (NetworkManagerOwner.SceneManager.IsSceneEventInProgress()) { return; } - var isAuthority = HasAuthority; SceneOriginHandle = scene.handle; // non-authority needs to update the NetworkSceneHandle - if (!isAuthority && NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) + if (!m_HasAuthority && NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) { NetworkSceneHandle = NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle]; } - else if (isAuthority) + else if (m_HasAuthority) { // Since the authority is the source of truth for the NetworkSceneHandle, // the NetworkSceneHandle is the same as the SceneOriginHandle. @@ -3718,8 +3856,8 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) NetworkSceneHandle = SceneOriginHandle; } } - else // Otherwise, the client did not find the client to server scene handle - if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) + // Otherwise, the client did not find the client to server scene handle + else if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) { // There could be a scenario where a user has some client-local scene loaded that they migrate the NetworkObject // into, but that scenario seemed very edge case and under most instances a user should be notified that this @@ -3732,7 +3870,7 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) OnMigratedToNewScene?.Invoke(); // Only the authority side will notify clients of non-parented NetworkObject scene changes - if (isAuthority && notify && !transform.parent) + if (m_HasAuthority && notify && !transform.parent) { NetworkManagerOwner.SceneManager.NotifyNetworkObjectSceneChanged(this); } diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs index 12fd9a60bb..66a7df1f96 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs @@ -1,8 +1,10 @@ +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using Unity.Netcode.Logging; using UnityEngine; +using UnityEngine.Assertions; namespace Unity.Netcode { @@ -101,6 +103,8 @@ internal static void ConfigureIntegrationTestLogging(NetworkManager networkManag /// The message to log [HideInCallstack] public static void LogErrorServer(string message) => s_Log.ErrorServer(new Context(LogLevel.Error, message, true)); + [HideInCallstack] + internal static void LogErrorServer(Context context) => s_Log.ErrorServer(context); internal static LogType GetMessageLogType(UnityEngine.LogType engineLogType) { @@ -113,14 +117,14 @@ internal static LogType GetMessageLogType(UnityEngine.LogType engineLogType) }; } - private const string k_SenderId = "SenderId"; internal static Context BuildContextForServerMessage([NotNull] NetworkManager networkManager, LogLevel level, ulong senderId, string message) { - var ctx = new Context(level, message, true).AddInfo(k_SenderId, senderId); - if (TryGetNetworkObjectName(networkManager, message, out var name)) + var ctx = new Context(level, message, true).AddTag("Received log from client").AddInfo(k_SenderId, senderId); + var networkObject = TryGetNetworkObject(networkManager, message); + if (networkObject != null) { - ctx.AddTag(name); + ctx.AddNetworkObject(networkObject); } return ctx; } @@ -133,30 +137,48 @@ internal enum LogType : byte None } - private static readonly Regex k_GlobalObjectIdHash = new($@"\[{nameof(NetworkObject.GlobalObjectIdHash)}=(\d+)\]", RegexOptions.Compiled); + private static readonly Regex k_NetworkObjectId = new($@"\[{nameof(NetworkObject.NetworkObjectId)}:(\d+)\]", RegexOptions.Compiled); + private static readonly Regex k_GlobalObjectIdHash = new($@"\[{nameof(NetworkObject.GlobalObjectIdHash)}:(\d+)\]", RegexOptions.Compiled); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryGetNetworkObjectName([NotNull] NetworkManager networkManager, string message, out string name) + private static NetworkObject TryGetNetworkObject([NotNull] NetworkManager networkManager, string message) { - name = null; + if (k_NetworkObjectId.IsMatch(message)) + { + var stringId = k_NetworkObjectId.Match(message).Groups[1].Value; + if (ulong.TryParse(stringId, out var networkObjectId) && networkObjectId > 0 && networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var networkObject)) + { + return networkObject; + } + } + if (!k_GlobalObjectIdHash.IsMatch(message)) { - return false; + return null; } var stringHash = k_GlobalObjectIdHash.Match(message).Groups[1].Value; if (!ulong.TryParse(stringHash, out var globalObjectIdHash)) { - return false; + return null; } - if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(globalObjectIdHash, out var networkObject)) + NetworkObject matchingObject = null; + foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList) { - return false; + if (networkObject.GlobalObjectIdHash == globalObjectIdHash) + { + matchingObject = networkObject; + } } - name = networkObject.name; - return true; + return matchingObject; } + [HideInCallstack] + [Conditional("NETCODE_INTERNAL")] + internal static void InternalAssert(bool condition, string message) + { + Assert.IsTrue(condition, message); + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs index 21413a4fae..8d8379dc05 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs @@ -148,6 +148,7 @@ internal static Dictionary GetMessageTypesMap() [InitializeOnLoadMethod] public static void NotifyOnPlayStateChange() { + EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 17669a335f..2a7eee8be7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -160,7 +160,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { sobj.AddObserver(OwnerClientId); // In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized. - var serializedObject = sobj.Serialize(OwnerClientId, IsDistributedAuthority); + var serializedObject = sobj.SerializeSpawnedObject(OwnerClientId, IsDistributedAuthority); serializedObject.Serialize(writer); ++sceneObjectCount; } @@ -344,7 +344,7 @@ public void Handle(ref NetworkContext context) { var serializedObject = new NetworkObject.SerializedObject(); serializedObject.Deserialize(m_ReceivedSceneObjectData); - NetworkObject.Deserialize(serializedObject, m_ReceivedSceneObjectData, networkManager); + NetworkObject.DeserializeAndSpawnObject(serializedObject, m_ReceivedSceneObjectData, networkManager); } if (networkManager.DistributedAuthorityMode && networkManager.AutoSpawnPlayerPrefabClientSide) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index 6d7d28bf73..0238e0ddb7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Runtime.CompilerServices; +using Unity.Netcode.Logging; namespace Unity.Netcode { @@ -190,7 +191,12 @@ internal static void CreateObject(ref NetworkManager networkManager, ulong sende { if (!networkManager.DistributedAuthorityMode) { - networkObject = NetworkObject.Deserialize(serializedObject, networkVariableData, networkManager); + networkObject = NetworkObject.DeserializeAndSpawnObject(serializedObject, networkVariableData, networkManager); + if (networkObject == null) + { + networkManager.Log.ErrorServer(new Context(LogLevel.Developer, $"Failed to deserialize {nameof(NetworkObject)}.").AddInfo(nameof(NetworkObject.GlobalObjectIdHash), serializedObject.Hash).AddInfo(nameof(NetworkObject.NetworkObjectId), serializedObject.NetworkObjectId)); + return; + } } else { @@ -198,25 +204,27 @@ internal static void CreateObject(ref NetworkManager networkManager, ulong sende var hasNewObserverIdList = newObserverIds != null && newObserverIds.Length > 0; // Depending upon visibility of the NetworkObject and the client in question, it could be that // this client already has visibility of this NetworkObject - if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(serializedObject.NetworkObjectId)) + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(serializedObject.NetworkObjectId, out networkObject)) { - // If so, then just get the local instance - networkObject = networkManager.SpawnManager.SpawnedObjects[serializedObject.NetworkObjectId]; - // This should not happen, logging error just in case if (hasNewObserverIdList && newObserverIds.Contains(networkManager.LocalClientId)) { NetworkLog.LogErrorServer($"[{nameof(CreateObjectMessage)}][Duplicate-Broadcast] Detected duplicated object creation for {serializedObject.NetworkObjectId}!"); } - else // Trap to make sure the owner is not receiving any messages it sent - if (networkManager.CMBServiceConnection && networkManager.LocalClientId == networkObject.OwnerClientId) + // Trap to make sure the owner is not receiving any messages it sent + else if (networkManager.CMBServiceConnection && networkManager.LocalClientId == networkObject.OwnerClientId) { NetworkLog.LogWarning($"[{nameof(CreateObjectMessage)}][Client-{networkManager.LocalClientId}][Duplicate-CreateObjectMessage][Client Is Owner] Detected duplicated object creation for {networkObject.name}-{serializedObject.NetworkObjectId}!"); } } else { - networkObject = NetworkObject.Deserialize(serializedObject, networkVariableData, networkManager, true); + networkObject = NetworkObject.DeserializeAndSpawnObject(serializedObject, networkVariableData, networkManager, true); + if (networkObject == null) + { + networkManager.Log.ErrorServer(new Context(LogLevel.Developer, $"Failed to deserialize {nameof(NetworkObject)}.").AddInfo(nameof(NetworkObject.GlobalObjectIdHash), serializedObject.Hash).AddInfo(nameof(NetworkObject.NetworkObjectId), serializedObject.NetworkObjectId)); + return; + } } // DA - NGO CMB SERVICE NOTES: @@ -233,27 +241,6 @@ internal static void CreateObject(ref NetworkManager networkManager, ulong sende // Mock CMB Service and forward to all clients if (networkManager.DAHost) { - // DA - NGO CMB SERVICE NOTES: - // (*** See above notes fist ***) - // If it is a player object freshly spawning and one or more clients all connect at the exact same time (i.e. received on effectively - // the same frame), then we need to check the observers list to make sure all players are visible upon first spawning. At a later date, - // for area of interest we will need to have some form of follow up "observer update" message to cull out players not within each - // player's AOI. - if (networkObject.IsPlayerObject && hasNewObserverIdList && clientList.Count != observerIds.Length) - { - // For same-frame newly spawned players that might not be aware of all other players, update the player's observer - // list. - observerIds = clientList.ToArray(); - } - - var createObjectMessage = new CreateObjectMessage() - { - ObjectInfo = serializedObject, - m_ReceivedNetworkVariableData = networkVariableData, - ObserverIds = hasObserverIdList ? observerIds : null, - NetworkObjectId = networkObject.NetworkObjectId, - IncludesSerializedObject = true, - }; foreach (var clientId in clientList) { // DA - NGO CMB SERVICE NOTES: @@ -269,16 +256,12 @@ internal static void CreateObject(ref NetworkManager networkManager, ulong sende // If this included a list of new observers and the targeted clientId is one of the observers, then send the serialized data. // Otherwise, the targeted clientId has already has visibility (i.e. it is already spawned) and so just send the updated // observers list to that client's instance. - createObjectMessage.IncludesSerializedObject = hasNewObserverIdList && newObserverIds.Contains(clientId); - networkManager.SpawnManager.SendSpawnCallForObject(clientId, networkObject); } } } - if (networkObject != null) - { - networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, messageSize); - } + + networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, messageSize); } catch (System.Exception ex) { diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs index e1242378dd..00dd2978b6 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs @@ -243,11 +243,23 @@ public void Read(FastBufferReader reader, ref T value) public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) { + if (UserNetworkVariableSerialization.WriteDelta != null && UserNetworkVariableSerialization.ReadDelta != null) + { + UserNetworkVariableSerialization.WriteDelta(writer, value, previousValue); + return; + } + Write(writer, ref value); } public void ReadDelta(FastBufferReader reader, ref T value) { + if (UserNetworkVariableSerialization.WriteDelta != null && UserNetworkVariableSerialization.ReadDelta != null) + { + UserNetworkVariableSerialization.ReadDelta(reader, ref value); + return; + } + Read(reader, ref value); } @@ -258,6 +270,12 @@ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, ou public void Duplicate(in T value, ref T duplicatedValue) { + if (UserNetworkVariableSerialization.DuplicateValue != null) + { + UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue); + return; + } + duplicatedValue = value; } } @@ -959,6 +977,12 @@ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, ou public void Duplicate(in T value, ref T duplicatedValue) { + if (UserNetworkVariableSerialization.DuplicateValue != null) + { + UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue); + return; + } + duplicatedValue = value; } } @@ -1128,6 +1152,12 @@ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, ou public void Duplicate(in T value, ref T duplicatedValue) { + if (UserNetworkVariableSerialization.DuplicateValue != null) + { + UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue); + return; + } + using var writer = new FastBufferWriter(256, Allocator.Temp, int.MaxValue); var refValue = value; Write(writer, ref refValue); diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs index 76ea8feb04..f7ce87d521 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs @@ -359,8 +359,8 @@ public void SetClientSynchronizationMode(ref NetworkManager networkManager, Load } return; } - else // Warn users if they are changing this after there are clients already connected and synchronized - if (!networkManager.DistributedAuthorityMode && networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode) + // Warn users if they are changing this after there are clients already connected and synchronized + else if (!networkManager.DistributedAuthorityMode && networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 6ab98a8e88..11d9cdbc99 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -143,7 +143,7 @@ public class SceneEvent /// /// Main class for managing network scenes when is enabled. - /// Uses the message to communicate between the server and client(s) + /// Uses the SceneEventMessage message to communicate SceneEventData between the server and client(s) /// [Serializable] public class NetworkSceneManager : IDisposable @@ -162,8 +162,7 @@ public class NetworkSceneManager : IDisposable /// /// The delegate callback definition for scene event notifications.
/// See also:
- ///
- /// + /// ///
/// SceneEvent which contains information about the scene event, including type, progress, and scene details public delegate void SceneEventDelegate(SceneEvent sceneEvent); @@ -1035,7 +1034,7 @@ internal void SetTheSceneBeingSynchronized(NetworkSceneHandle serverSceneHandle) // Most common scenario for DontDestroyOnLoad is when NetworkManager is set to not be destroyed if (serverSceneHandle == DontDestroyOnLoadScene.handle) { - SceneBeingSynchronized = NetworkManager.gameObject.scene; + SceneBeingSynchronized = DontDestroyOnLoadScene; return; } else @@ -1952,6 +1951,21 @@ private void OnClientLoadedScene(uint sceneEventId, Scene scene) /// internal List ClientConnectionQueue = new List(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddSceneToClientSynchronization(ref SceneEventData sceneEventData, ref Scene scene) + { + // If we are just a normal client and in distributed authority mode, then always use the known server scene handle + if (NetworkManager.DistributedAuthorityMode && NetworkManager.CMBServiceConnection) + { + sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), ClientSceneHandleToServerSceneHandle[scene.handle]); + } + else + { + sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), scene.handle); + } + } + /// /// Server Side: /// This is used for players that have just had their connection approved and will assure they are synchronized @@ -2002,61 +2016,53 @@ internal void SynchronizeNetworkObjects(ulong clientId, bool synchronizingServic // Organize how (and when) we serialize our NetworkObjects var hasSynchronizedActive = false; - for (int i = 0; i < SceneManager.sceneCount; i++) - { - var scene = SceneManager.GetSceneAt(i); - // NetworkSceneManager does not synchronize scenes that are not loaded by NetworkSceneManager - // unless the scene in question is the currently active scene. - if (ExcludeSceneFromSychronization != null && !ExcludeSceneFromSychronization(scene)) + // It is possible a user might not want to synchronize the active scene, so we will check to see if it is valid before adding it to the synchronization list. + // !! Important !! + // The active scene MUST always be the first scene in the synchronization list. + if (ValidateSceneBeforeLoading(activeScene.buildIndex, activeScene.name, sceneEventData.LoadSceneMode)) + { + sceneEventData.SceneHash = SceneHashFromNameOrPath(activeScene.path); + if (sceneEventData.SceneHash == sceneEventData.ActiveSceneHash) { - continue; + hasSynchronizedActive = true; } - if (scene == DontDestroyOnLoadScene) + // If we are just a normal client, then always use the server scene handle + if (NetworkManager.DistributedAuthorityMode) { - continue; + sceneEventData.SenderClientId = NetworkManager.LocalClientId; + sceneEventData.SceneHandle = ClientSceneHandleToServerSceneHandle[activeScene.handle]; } - - // This would depend upon whether we are additive or not - // If we are the base scene, then we set the root scene index; - if (activeScene == scene) + else { - if (!ValidateSceneBeforeLoading(scene.buildIndex, scene.name, sceneEventData.LoadSceneMode)) - { - continue; - } - sceneEventData.SceneHash = SceneHashFromNameOrPath(scene.path); - if (sceneEventData.SceneHash == sceneEventData.ActiveSceneHash) - { - hasSynchronizedActive = true; - } - - // If we are just a normal client, then always use the server scene handle - if (NetworkManager.DistributedAuthorityMode) - { - sceneEventData.SenderClientId = NetworkManager.LocalClientId; - sceneEventData.SceneHandle = ClientSceneHandleToServerSceneHandle[scene.handle]; - } - else - { - sceneEventData.SceneHandle = scene.handle; - } + sceneEventData.SceneHandle = activeScene.handle; } - else if (!ValidateSceneBeforeLoading(scene.buildIndex, scene.name, LoadSceneMode.Additive)) + AddSceneToClientSynchronization(ref sceneEventData, ref activeScene); + } + + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + // Skip adding the active scene at this point as we are just adding all other additively loaded scenes to the synchronization list. + // Skip adding the dont destroy on load scene as that is never synchronized. + if ((scene.handle == activeScene.handle) || (scene == DontDestroyOnLoadScene)) { continue; } - // If we are just a normal client and in distributed authority mode, then always use the known server scene handle - if (NetworkManager.DistributedAuthorityMode && NetworkManager.CMBServiceConnection) + // NetworkSceneManager does not synchronize scenes that are not loaded by NetworkSceneManager + // unless the scene in question is the currently active scene. + if (ExcludeSceneFromSychronization != null && !ExcludeSceneFromSychronization(scene)) { - sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), ClientSceneHandleToServerSceneHandle[scene.handle]); + continue; } - else + + if (!ValidateSceneBeforeLoading(scene.buildIndex, scene.name, LoadSceneMode.Additive)) { - sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), scene.handle); + continue; } + AddSceneToClientSynchronization(ref sceneEventData, ref scene); } if (!hasSynchronizedActive && NetworkManager.CMBServiceConnection && synchronizingService) @@ -2109,9 +2115,12 @@ private void OnClientBeginSync(uint sceneEventId) var sceneHash = sceneEventData.GetNextSceneSynchronizationHash(); var sceneHandle = sceneEventData.GetNextSceneSynchronizationHandle(); var sceneName = SceneNameFromHash(sceneHash); + var activeSceneName = SceneNameFromHash(sceneEventData.ActiveSceneHash); var activeScene = SceneManager.GetActiveScene(); - var loadSceneMode = sceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive; + var activeSceneLoaded = activeSceneName == activeScene.name; + + var loadSceneMode = sceneHash == sceneEventData.SceneHash && !activeSceneLoaded ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive; // Store the sceneHandle and hash sceneEventData.NetworkSceneHandle = sceneHandle; @@ -2251,8 +2260,6 @@ private void SynchronizeNetworkObjectScene() { networkObject.SceneOriginHandle = ServerSceneHandleToClientSceneHandle[networkObject.NetworkSceneHandle]; - - // If the NetworkObject does not have a parent and is not in the same scene as it is on the server side, then find the right scene // and move it to that scene. if (networkObject.gameObject.scene.handle != networkObject.SceneOriginHandle && networkObject.transform.parent == null) @@ -2260,11 +2267,6 @@ private void SynchronizeNetworkObjectScene() if (ScenesLoaded.ContainsKey(networkObject.SceneOriginHandle)) { var scene = ScenesLoaded[networkObject.SceneOriginHandle]; - if (scene == DontDestroyOnLoadScene) - { - Debug.Log($"{networkObject.gameObject.name} migrating into DDOL!"); - } - SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene); } else if (NetworkManager.LogLevel <= LogLevel.Normal) @@ -2894,6 +2896,12 @@ internal bool IsSceneUnloading(NetworkObject networkObject) /// internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) { + if (networkObject.NetworkManagerOwner != NetworkManager) + { + Debug.Log($"!!!!!!!!!!!!! Integration test is registering for scene migration for instances outside of the bounds of this NetworkManager context !!!!!!!!!!!!!"); + return; + } + // Really, this should never happen but in case it does if (!networkObject.HasAuthority) { @@ -2917,7 +2925,7 @@ internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) // Ignore if the scene is the currently active scene and the NetworkObject is auto synchronizing/migrating // to the currently active scene. - if (networkObject.gameObject.scene == SceneManager.GetActiveScene() && networkObject.ActiveSceneSynchronization) + if (networkObject.gameObject.scene.name == SceneManager.GetActiveScene().name && networkObject.ActiveSceneSynchronization) { return; } @@ -2926,6 +2934,13 @@ internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) // Note: This does not apply to SceneEventType.Synchronize since synchronization isn't a global connected client event. if (IsSceneEventInProgress()) { + Debug.Log($"{networkObject.name} scene event in progress -- ignoring!"); + return; + } + + if (IsSceneUnloading(networkObject)) + { + Debug.Log($"{networkObject.name} scene unloading in progress -- ignoring!"); return; } @@ -3050,7 +3065,15 @@ internal void CheckForAndSendNetworkObjectSceneChanged() // Some NetworkObjects still exist, send the message var sceneEvent = BeginSceneEvent(); sceneEvent.SceneEventType = SceneEventType.ObjectSceneChanged; - SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.LocalClientId).ToArray()); + // SendSceneEventData can throw an exception. We need to wrap this and recover from the exception gracefully. + try + { + SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.LocalClientId).ToArray()); + } + catch (Exception ex) + { + Debug.LogException(ex); + } ObjectsMigratedIntoNewScene.Clear(); EndSceneEvent(sceneEvent.SceneEventId); } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 744a723c22..99445d93d6 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -131,21 +131,21 @@ internal class SceneEventData : IDisposable /// was synchronizing (if so server will send another message back to the client informing the client of NetworkObjects to remove) /// spawned during an initial synchronization. /// - private List m_NetworkObjectsSync = new List(); + private readonly List m_NetworkObjectsSync = new List(); - private List m_DespawnedInSceneObjectsSync = new List(); + private readonly List m_DespawnedInSceneObjectsSync = new List(); /// /// Server Side Re-Synchronization: /// If there happens to be NetworkObjects in the final Event_Sync_Complete message that are no longer spawned, /// the server will compile a list and send back an Event_ReSync message to the client. /// - private List m_NetworkObjectsToBeRemoved = new List(); + private readonly List m_NetworkObjectsToBeRemoved = new List(); private bool m_HasInternalBuffer; - internal FastBufferReader InternalBuffer; + private FastBufferReader m_InternalBuffer; - private NetworkManager m_NetworkManager; + private readonly NetworkManager m_NetworkManager; internal List ClientsCompleted; internal List ClientsTimedOut; @@ -392,12 +392,12 @@ internal void AddDespawnedInSceneNetworkObjects() /// internal void AddNetworkObjectForSynch(uint sceneIndex, NetworkObject networkObject) { - if (!m_SceneNetworkObjects.ContainsKey(sceneIndex)) + if (!m_SceneNetworkObjects.TryGetValue(sceneIndex, out var sceneNetworkObject)) { - m_SceneNetworkObjects.Add(sceneIndex, new List()); + sceneNetworkObject = new List(); + m_SceneNetworkObjects.Add(sceneIndex, sceneNetworkObject); } - - m_SceneNetworkObjects[sceneIndex].Add(networkObject); + sceneNetworkObject.Add(networkObject); } /// @@ -572,7 +572,7 @@ internal void Serialize(FastBufferWriter writer) private unsafe void CopyInternalBuffer(ref FastBufferWriter writer) { - writer.WriteBytesSafe(InternalBuffer.GetUnsafePtrAtCurrentPosition(), InternalBuffer.Length); + writer.WriteBytesSafe(m_InternalBuffer.GetUnsafePtrAtCurrentPosition(), m_InternalBuffer.Length); } /// @@ -580,9 +580,9 @@ private unsafe void CopyInternalBuffer(ref FastBufferWriter writer) /// Called at the end of a event once the scene is loaded and scene placed NetworkObjects /// have been locally spawned /// - internal void WriteSceneSynchronizationData(FastBufferWriter writer) + private void WriteSceneSynchronizationData(FastBufferWriter writer) { - var builder = (StringBuilder)null; + StringBuilder builder = null; if (EnableSerializationLogs) { builder = new StringBuilder(); @@ -605,11 +605,10 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) return; } - // Size Place Holder -- Start + // Size Placeholder -- Start // !!NOTE!!: Since this is a placeholder to be set after we know how much we have written, // for stream offset purposes this MUST not be a packed value! writer.WriteValueSafe(0); - int totalBytes = 0; // Write the number of NetworkObjects we are serializing writer.WriteValueSafe(m_NetworkObjectsSync.Count); @@ -620,16 +619,14 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) var distributedAuthority = m_NetworkManager.DistributedAuthorityMode; // Serialize all NetworkObjects that are spawned - for (var i = 0; i < m_NetworkObjectsSync.Count; ++i) + foreach (var networkObject in m_NetworkObjectsSync) { - var networkObject = m_NetworkObjectsSync[i]; var noStart = writer.Position; // In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized. - var serializedObject = m_NetworkObjectsSync[i].Serialize(TargetClientId, distributedAuthority); + var serializedObject = networkObject.SerializeSpawnedObject(TargetClientId, distributedAuthority); serializedObject.Serialize(writer); var noStop = writer.Position; - totalBytes += noStop - noStart; if (EnableSerializationLogs) { var offStart = noStart - (positionStart + sizeof(int)); @@ -646,16 +643,13 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) // Write the number of despawned in-scene placed NetworkObjects writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count); // Write the scene handle and GlobalObjectIdHash value - for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i) + foreach (var despawnedInSceneObject in m_DespawnedInSceneObjectsSync) { - var noStart = writer.Position; - writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); - writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); - var noStop = writer.Position; - totalBytes += noStop - noStart; + writer.WriteValueSafe(despawnedInSceneObject.GetSceneOriginHandle()); + writer.WriteValueSafe(despawnedInSceneObject.GlobalObjectIdHash); } - // Size Place Holder -- End + // Size Placeholder -- End var positionEnd = writer.Position; var bytesWritten = (uint)(positionEnd - (positionStart + sizeof(uint))); writer.Seek(positionStart); @@ -674,12 +668,12 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) /// have been locally spawned /// Maximum number of objects that could theoretically be synchronized is 65536 /// - internal void SerializeScenePlacedObjects(FastBufferWriter writer) + private void SerializeScenePlacedObjects(FastBufferWriter writer) { - var numberOfObjects = (ushort)0; + ushort numberOfObjects = 0; var headPosition = writer.Position; - // Write our count place holder (must not be packed!) + // Write our count placeholder (must not be packed!) writer.WriteValueSafe((ushort)0); var distributedAuthority = m_NetworkManager.DistributedAuthorityMode; // If distributed authority mode and sending to the service, then ignore observers @@ -702,10 +696,10 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) SortObjectsToSync(); // Serialize the sorted objects to sync. - foreach (var objectToSycn in m_NetworkObjectsSync) + foreach (var objectToSync in m_NetworkObjectsSync) { // Serialize the NetworkObject - var serializedObject = objectToSycn.Serialize(TargetClientId, distributedAuthority); + var serializedObject = objectToSync.SerializeSpawnedObject(TargetClientId, distributedAuthority); serializedObject.Serialize(writer); numberOfObjects++; } @@ -713,10 +707,10 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) // Write the number of despawned in-scene placed NetworkObjects writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count); // Write the scene handle and GlobalObjectIdHash value - for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i) + foreach (var despawnedInSceneObject in m_DespawnedInSceneObjectsSync) { - writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); - writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); + writer.WriteValueSafe(despawnedInSceneObject.GetSceneOriginHandle()); + writer.WriteValueSafe(despawnedInSceneObject.GlobalObjectIdHash); } var tailPosition = writer.Position; @@ -803,7 +797,7 @@ internal void Deserialize(FastBufferReader reader) // be processed once we are done loading. m_HasInternalBuffer = true; // We use Allocator.Persistent since scene loading could take longer than 4 frames - InternalBuffer = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, reader.Length - reader.Position); + m_InternalBuffer = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, reader.Length - reader.Position); } break; } @@ -829,7 +823,7 @@ internal void Deserialize(FastBufferReader reader) /// into the internal buffer to be used throughout the synchronization process. /// /// - internal void CopySceneSynchronizationData(FastBufferReader reader) + private void CopySceneSynchronizationData(FastBufferReader reader) { m_NetworkObjectsSync.Clear(); reader.ReadValueSafe(out uint[] scenesToSynchronize); @@ -851,10 +845,10 @@ internal void CopySceneSynchronizationData(FastBufferReader reader) m_HasInternalBuffer = true; // We use Allocator.Persistent since scene synchronization will most likely take longer than 4 frames - InternalBuffer = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, sizeToCopy); + m_InternalBuffer = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, sizeToCopy); if (EnableSerializationLogs) { - LogArray(InternalBuffer.ToArray()); + LogArray(m_InternalBuffer.ToArray()); } } } @@ -869,12 +863,12 @@ internal void DeserializeScenePlacedObjects() try { // is not packed! - InternalBuffer.ReadValueSafe(out ushort newObjectsCount); + m_InternalBuffer.ReadValueSafe(out ushort newObjectsCount); var sceneObjects = new List(); for (ushort i = 0; i < newObjectsCount; i++) { var serializedObject = new NetworkObject.SerializedObject(); - serializedObject.Deserialize(InternalBuffer); + serializedObject.Deserialize(m_InternalBuffer); if (serializedObject.IsSceneObject) { @@ -882,9 +876,9 @@ internal void DeserializeScenePlacedObjects() m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(serializedObject.NetworkSceneHandle); } - var networkObject = NetworkObject.Deserialize(serializedObject, InternalBuffer, m_NetworkManager); + var networkObject = NetworkObject.DeserializeAndSpawnObject(serializedObject, m_InternalBuffer, m_NetworkManager); - if (serializedObject.IsSceneObject) + if (serializedObject.IsSceneObject && networkObject != null) { sceneObjects.Add(networkObject); } @@ -901,7 +895,7 @@ internal void DeserializeScenePlacedObjects() } finally { - InternalBuffer.Dispose(); + m_InternalBuffer.Dispose(); m_HasInternalBuffer = false; } } @@ -913,7 +907,7 @@ internal void DeserializeScenePlacedObjects() /// client handles any returned values by the server. /// /// - internal void ReadClientReSynchronizationData(FastBufferReader reader) + private void ReadClientReSynchronizationData(FastBufferReader reader) { reader.ReadValueSafe(out uint[] networkObjectsToRemove); @@ -950,7 +944,7 @@ internal void ReadClientReSynchronizationData(FastBufferReader reader) /// the server will compile a list and send back an Event_ReSync message to the client. /// /// - internal void WriteClientReSynchronizationData(FastBufferWriter writer) + private void WriteClientReSynchronizationData(FastBufferWriter writer) { //Write how many objects need to be removed writer.WriteValueSafe(m_NetworkObjectsToBeRemoved.ToArray()); @@ -964,7 +958,7 @@ internal void WriteClientReSynchronizationData(FastBufferWriter writer) /// internal bool ClientNeedsReSynchronization() { - return (m_NetworkObjectsToBeRemoved.Count > 0); + return m_NetworkObjectsToBeRemoved.Count > 0; } /// @@ -974,7 +968,7 @@ internal bool ClientNeedsReSynchronization() /// have since been despawned. /// /// - internal void CheckClientSynchronizationResults(FastBufferReader reader) + private void CheckClientSynchronizationResults(FastBufferReader reader) { m_NetworkObjectsToBeRemoved.Clear(); reader.ReadValueSafe(out uint networkObjectIdCount); @@ -991,12 +985,12 @@ internal void CheckClientSynchronizationResults(FastBufferReader reader) /// /// Client Side: /// During the deserialization process of the servers Event_Sync, the client builds a list of - /// all NetworkObjectIds that were spawned. Upon responding to the server with the Event_Sync_Complete + /// all NetworkObjectIds that were spawned. Upon responding to the server with the Event_Sync_Complete, /// this list is included for the server to review over and determine if the client needs a minor resynchronization /// of NetworkObjects that might have been despawned while the client was processing the Event_Sync. /// /// - internal void WriteClientSynchronizationResults(FastBufferWriter writer) + private void WriteClientSynchronizationResults(FastBufferWriter writer) { //Write how many objects were spawned writer.WriteValueSafe((uint)m_NetworkObjectsSync.Count); @@ -1013,14 +1007,14 @@ internal void WriteClientSynchronizationResults(FastBufferWriter writer) private void DeserializeDespawnedInScenePlacedNetworkObjects() { // Process all de-spawned in-scene NetworkObjects for this network session - InternalBuffer.ReadValueSafe(out int despawnedObjectsCount); + m_InternalBuffer.ReadValueSafe(out int despawnedObjectsCount); var sceneCache = new Dictionary>(); for (int i = 0; i < despawnedObjectsCount; i++) { // We just need to get the scene - InternalBuffer.ReadValueSafe(out NetworkSceneHandle networkSceneHandle); - InternalBuffer.ReadValueSafe(out uint globalObjectIdHash); + m_InternalBuffer.ReadValueSafe(out NetworkSceneHandle networkSceneHandle); + m_InternalBuffer.ReadValueSafe(out uint globalObjectIdHash); // Check if we already have processed the objects in this scene if (!sceneCache.TryGetValue(networkSceneHandle, out var sceneRelativeNetworkObjects)) @@ -1061,15 +1055,10 @@ private void DeserializeDespawnedInScenePlacedNetworkObjects() // Since this is a NetworkObject that was never spawned, we just need to send a notification // out that it was despawned so users can make adjustments despawnedObject.InvokeBehaviourNetworkDespawn(); - if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash)) - { - m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary()); - } - if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(despawnedObject.GetSceneOriginHandle())) - { - m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(despawnedObject.GetSceneOriginHandle(), despawnedObject); - } + m_NetworkManager.SceneManager.ScenePlacedObjects.TryAdd(globalObjectIdHash, new Dictionary()); + + m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].TryAdd(despawnedObject.GetSceneOriginHandle(), despawnedObject); } else { @@ -1087,7 +1076,7 @@ private void DeserializeDespawnedInScenePlacedNetworkObjects() /// internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) { - var builder = (StringBuilder)null; + StringBuilder builder = null; if (EnableSerializationLogs) { builder = new StringBuilder(); @@ -1096,17 +1085,17 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) try { // Process all spawned NetworkObjects for this network session - InternalBuffer.ReadValueSafe(out int newObjectsCount); + m_InternalBuffer.ReadValueSafe(out int newObjectsCount); if (EnableSerializationLogs) { - builder.AppendLine($"[Read][Synchronize Objects][WPos: {InternalBuffer.Position}][NO-Count: {newObjectsCount}] Begin:"); + builder.AppendLine($"[Read][Synchronize Objects][WPos: {m_InternalBuffer.Position}][NO-Count: {newObjectsCount}] Begin:"); } for (int i = 0; i < newObjectsCount; i++) { - var noStart = InternalBuffer.Position; + var noStart = m_InternalBuffer.Position; var serializedObject = new NetworkObject.SerializedObject(); - serializedObject.Deserialize(InternalBuffer); + serializedObject.Deserialize(m_InternalBuffer); #if UNIFIED_NETCODE // This handles the case where a NetworkObject is serialized with a ghost component but the ghost isn't actually included in @@ -1127,21 +1116,24 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) { m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(serializedObject.NetworkSceneHandle); } - var spawnedNetworkObject = NetworkObject.Deserialize(serializedObject, InternalBuffer, networkManager); + + var spawnedNetworkObject = NetworkObject.DeserializeAndSpawnObject(serializedObject, m_InternalBuffer, networkManager); + if (spawnedNetworkObject == null) + { + continue; + } + + var noStop = m_InternalBuffer.Position; if (EnableSerializationLogs) { - var noStop = InternalBuffer.Position; builder.AppendLine($"[Head: {noStart}][Tail: {noStop}][Size: {noStop - noStart}][{spawnedNetworkObject.name}][NID-{spawnedNetworkObject.NetworkObjectId}][Children: {spawnedNetworkObject.ChildNetworkBehaviours.Count}]"); - LogArray(InternalBuffer.ToArray(), noStart, noStop, builder); + LogArray(m_InternalBuffer.ToArray(), noStart, noStop, builder); } // If we failed to deserialize the NetworkObject then don't add null to the list - if (spawnedNetworkObject != null) + if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject)) { - if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject)) - { - m_NetworkObjectsSync.Add(spawnedNetworkObject); - } + m_NetworkObjectsSync.Add(spawnedNetworkObject); } } if (EnableSerializationLogs) @@ -1172,7 +1164,7 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) } finally { - InternalBuffer.Dispose(); + m_InternalBuffer.Dispose(); m_HasInternalBuffer = false; } } @@ -1181,7 +1173,7 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) /// Writes the all clients loaded or unloaded completed and timed out lists /// /// - internal void WriteSceneEventProgressDone(FastBufferWriter writer) + private void WriteSceneEventProgressDone(FastBufferWriter writer) { writer.WriteValueSafe((ushort)ClientsCompleted.Count); foreach (var clientId in ClientsCompleted) @@ -1200,7 +1192,7 @@ internal void WriteSceneEventProgressDone(FastBufferWriter writer) /// Reads the all clients loaded or unloaded completed and timed out lists /// /// - internal void ReadSceneEventProgressDone(FastBufferReader reader) + private void ReadSceneEventProgressDone(FastBufferReader reader) { reader.ReadValueSafe(out ushort completedCount); ClientsCompleted = new List(); @@ -1229,31 +1221,51 @@ internal void ReadSceneEventProgressDone(FastBufferReader reader) private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer) { var sceneManager = m_NetworkManager.SceneManager; - var ownerId = m_NetworkManager.LocalClientId; + var networkManagerClientId = m_NetworkManager.LocalClientId; if (IsForwarding) { - ownerId = m_OwnerId; + networkManagerClientId = m_OwnerId; } // Write the owner identifier - writer.WriteValueSafe(ownerId); + writer.WriteValueSafe(networkManagerClientId); - // Write the number of scene handles - writer.WriteValueSafe(sceneManager.ObjectsMigratedIntoNewScene.Count); + // Create a place holder for the number of entries written. + // Distributed authority this could end up being just a single entry for + // one of several scenes loaded. As such, we need to count how many entries + // are actually written. + var countPosition = writer.Position; + writer.WriteValueSafe(0); + var entriesWritten = 0; foreach (var sceneHandleObjects in sceneManager.ObjectsMigratedIntoNewScene) { - if (!sceneManager.ObjectsMigratedIntoNewScene[sceneHandleObjects.Key].ContainsKey(ownerId)) + // Since these are separated by scene then owner, there could be scenes that have + // no changes. + if (!sceneHandleObjects.Value.ContainsKey(networkManagerClientId)) { - throw new Exception($"Trying to send object scene migration for Client-{ownerId} but the client has no entries to send!"); + continue; } // Write the scene handle writer.WriteValueSafe(sceneHandleObjects.Key); // Write the number of NetworkObjectIds to expect - writer.WriteValueSafe(sceneHandleObjects.Value[ownerId].Count); - foreach (var networkObject in sceneHandleObjects.Value[ownerId]) + writer.WriteValueSafe(sceneHandleObjects.Value[networkManagerClientId].Count); + foreach (var networkObject in sceneHandleObjects.Value[networkManagerClientId]) { writer.WriteValueSafe(networkObject.NetworkObjectId); } + entriesWritten++; + } + if (entriesWritten == 0) + { + throw new Exception($"Trying to send object scene migration for Client-{networkManagerClientId} but the client has no entries to send!"); + } + else + { + // Write the number of entries written + var endPosition = writer.Position; + writer.Seek(countPosition); + writer.WriteValueSafe(entriesWritten); + writer.Seek(endPosition); } } @@ -1266,40 +1278,31 @@ private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader) var sceneManager = m_NetworkManager.SceneManager; var spawnManager = m_NetworkManager.SpawnManager; - var numberOfScenes = 0; - NetworkSceneHandle sceneHandle; - var objectCount = 0; - var networkObjectId = (ulong)0; - - var ownerID = (ulong)0; - reader.ReadValueSafe(out ownerID); + reader.ReadValueSafe(out ulong ownerID); m_OwnerId = ownerID; - reader.ReadValueSafe(out numberOfScenes); + reader.ReadValueSafe(out int numberOfScenes); for (int i = 0; i < numberOfScenes; i++) { - reader.ReadValueSafe(out sceneHandle); + reader.ReadValueSafe(out NetworkSceneHandle sceneHandle); if (!sceneManager.ObjectsMigratedIntoNewScene.TryGetValue(sceneHandle, out var migratedObjects)) { migratedObjects = new Dictionary>(); sceneManager.ObjectsMigratedIntoNewScene.Add(sceneHandle, migratedObjects); } - if (!migratedObjects.ContainsKey(ownerID)) - { - migratedObjects.Add(ownerID, new List()); - } + migratedObjects.TryAdd(ownerID, new List()); - reader.ReadValueSafe(out objectCount); + reader.ReadValueSafe(out int objectCount); for (int j = 0; j < objectCount; j++) { - reader.ReadValueSafe(out networkObjectId); - if (!spawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + reader.ReadValueSafe(out ulong networkObjectId); + if (!spawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var networkObject)) { NetworkLog.LogError($"[Object Scene Migration] Trying to synchronize NetworkObjectId ({networkObjectId}) but it was not spawned or no longer exists!!"); continue; } - var networkObject = spawnManager.SpawnedObjects[networkObjectId]; + // Add NetworkObject scene migration to ObjectsMigratedIntoNewScene dictionary that is processed migratedObjects[ownerID].Add(networkObject); } @@ -1315,15 +1318,8 @@ private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader) private void DeferObjectsMovedIntoNewScene(FastBufferReader reader) { var sceneManager = m_NetworkManager.SceneManager; - var spawnManager = m_NetworkManager.SpawnManager; - var ownerId = (ulong)0; - var numberOfScenes = 0; - NetworkSceneHandle sceneHandle; - var objectCount = 0; - var networkObjectId = (ulong)0; - - reader.ReadValueSafe(out ownerId); + reader.ReadValueSafe(out ulong ownerId); var deferredObjectsMovedEvent = new NetworkSceneManager.DeferredObjectsMovedEvent() { @@ -1331,17 +1327,16 @@ private void DeferObjectsMovedIntoNewScene(FastBufferReader reader) ObjectsMigratedTable = new Dictionary>(), }; - - reader.ReadValueSafe(out numberOfScenes); + reader.ReadValueSafe(out int numberOfScenes); for (int i = 0; i < numberOfScenes; i++) { - reader.ReadValueSafe(out sceneHandle); + reader.ReadValueSafe(out NetworkSceneHandle sceneHandle); var objectsMigrated = new List(); deferredObjectsMovedEvent.ObjectsMigratedTable.Add(sceneHandle, objectsMigrated); - reader.ReadValueSafe(out objectCount); + reader.ReadValueSafe(out int objectCount); for (int j = 0; j < objectCount; j++) { - reader.ReadValueSafe(out networkObjectId); + reader.ReadValueSafe(out ulong networkObjectId); objectsMigrated.Add(networkObjectId); } } @@ -1365,10 +1360,9 @@ internal void ProcessDeferredObjectSceneChangedEvents() migratedObjects = new Dictionary>(); sceneManager.ObjectsMigratedIntoNewScene.Add(keyEntry.Key, migratedObjects); } - if (!migratedObjects.ContainsKey(objectsMovedEvent.OwnerId)) - { - migratedObjects.Add(objectsMovedEvent.OwnerId, new List()); - } + + migratedObjects.TryAdd(objectsMovedEvent.OwnerId, new List()); + var objectList = migratedObjects[objectsMovedEvent.OwnerId]; foreach (var objectId in keyEntry.Value) { @@ -1378,9 +1372,9 @@ internal void ProcessDeferredObjectSceneChangedEvents() continue; } - if (!migratedObjects[objectsMovedEvent.OwnerId].Contains(networkObject)) + if (!objectList.Contains(networkObject)) { - migratedObjects[objectsMovedEvent.OwnerId].Add(networkObject); + objectList.Add(networkObject); } } } @@ -1396,7 +1390,7 @@ public void Dispose() { if (m_HasInternalBuffer) { - InternalBuffer.Dispose(); + m_InternalBuffer.Dispose(); m_HasInternalBuffer = false; } } diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs index d420ccd28e..d132f775fd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs @@ -564,14 +564,36 @@ public void ReadNetworkSerializableInPlace(ref T value) where T : INetworkSer } /// - /// Reads a string - /// NOTE: ALLOCATES + /// Validates the string's total byte count based on whether we are + /// using one or two byte characters. + /// + /// + /// Will throw an overflow exception if the size is greater than . + /// + /// Character count + /// If false(default) 2 byte characters and if true 1 byte characters + /// total size in bytes to read + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ValidateStringByteCount(int length, bool oneByteChars) + { + var readSize = oneByteChars ? length : length * sizeof(char); + if (int.MaxValue < (uint)readSize) + { + throw new OverflowException($"Invalid reader position detected when trying to read a string of size {(uint)readSize}! This can result from an error in the serialization. Ensure deserialization exactly matches what was serialized!"); + } + return readSize; + } + + /// + /// Commonly shared string read method between . /// - /// Stores the read string - /// Whether or not to use one byte per character. This will only allow ASCII - public unsafe void ReadValue(out string s, bool oneByteChars = false) + /// The output of the string read. + /// The number of characters in the string. + /// If false(default) 2 byte characters and if true 1 byte characters. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void ReadString(out string s, int length, bool oneByteChars) { - ReadLength(out int length); s = "".PadRight(length); int target = s.Length; fixed (char* native = s) @@ -592,56 +614,50 @@ public unsafe void ReadValue(out string s, bool oneByteChars = false) } /// - /// Reads a string. - /// NOTE: ALLOCATES - /// - /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking - /// for multiple reads at once by calling TryBeginRead. + /// Reads a string without bounds checking. + /// NOTE: This method ALLOCATES memory. /// + /// + /// This is the un-safe string read which requires invoking prior to invoking this method.
+ /// Using one byte characters only allows ASCII characters. + ///
/// Stores the read string - /// Whether or not to use one byte per character. This will only allow ASCII - public unsafe void ReadValueSafe(out string s, bool oneByteChars = false) + /// If false(default) 2 byte characters and if true 1 byte characters. + public unsafe void ReadValue(out string s, bool oneByteChars = false) { -#if DEBUG - if (Handle->InBitwiseContext) - { - throw new InvalidOperationException( - "Cannot use BufferReader in bytewise mode while in a bitwise context."); - } -#endif + ReadLength(out int length); - if (!TryBeginReadInternal(SizeOfLengthField())) - { - throw new OverflowException("Reading past the end of the buffer"); - } + // Validate the string's byte count based on the character count. + ValidateStringByteCount(length, oneByteChars); - ReadLength(out int length); + // Read the string + ReadString(out s, length, oneByteChars); + } + + /// + /// Reads a string after it performs bounds checking automatically. + /// NOTE: This method ALLOCATES memory. + /// + /// + /// This is the safe string read which invokes prior to reading the string.
+ /// Using one byte characters only allows ASCII characters. + ///
+ /// The string re the read string + /// If false(default) 2 byte characters and if true 1 byte characters. + public unsafe void ReadValueSafe(out string s, bool oneByteChars = false) + { + ReadLengthSafe(out int length); - if (!TryBeginReadInternal(length * (oneByteChars ? 1 : sizeof(char)))) + // Validate the string's byte count based on the character count and if it is valid begin reading based on the returned + // byte count. + if (!TryBeginReadInternal(ValidateStringByteCount(length, oneByteChars))) { throw new OverflowException("Reading past the end of the buffer"); } - s = "".PadRight(length); - int target = s.Length; - fixed (char* native = s) - { - if (oneByteChars) - { - for (int i = 0; i < target; ++i) - { - ReadByte(out byte b); - native[i] = (char)b; - } - } - else - { - ReadBytes((byte*)native, target * sizeof(char)); - } - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int SizeOfLengthField() => sizeof(uint); + // Read the string + ReadString(out s, length, oneByteChars); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ReadLengthSafe(out uint length) => ReadUnmanagedSafe(out length); diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index 735fa613b7..7b902a3a52 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -344,8 +344,8 @@ internal void HandleNetworkPrefabDestroy(NetworkObject networkObjectInstance) prefabInstanceHandler.Destroy(networkObjectInstance); } } - else // Otherwise the NetworkObject is the source NetworkPrefab - if (m_PrefabAssetToPrefabHandler.TryGetValue(networkObjectInstanceHash, out var prefabInstanceHandler)) + // Otherwise the NetworkObject is the source NetworkPrefab + else if (m_PrefabAssetToPrefabHandler.TryGetValue(networkObjectInstanceHash, out var prefabInstanceHandler)) { prefabInstanceHandler.Destroy(networkObjectInstance); } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index b04d512a98..0a45243cc4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -911,10 +911,7 @@ internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ow // If not, then there is an issue (user possibly didn't register the prefab properly?) if (networkPrefabReference == null) { - if (NetworkLog.CurrentLogLevel <= LogLevel.Error) - { - NetworkLog.LogErrorServer($"[{nameof(globalObjectIdHash)}={globalObjectIdHash}] Failed to create object locally. {nameof(NetworkPrefab)} could not be found. Is the prefab registered with {NetworkManager.name}?"); - } + NetworkManager.Log.ErrorServer(new Context(LogLevel.Error, $"Failed to create object locally. {nameof(NetworkPrefab)} could not be found. Is the prefab registered with this {nameof(NetworkManager)}").AddInfo(nameof(NetworkObject.GlobalObjectIdHash), globalObjectIdHash).AddTag(NetworkManager.name)); return null; } @@ -961,6 +958,7 @@ internal NetworkObject InstantiateNetworkPrefab([NotNull] GameObject networkPref /// /// For most cases this is client-side only, except when the server is spawning a player. /// + [return: MaybeNull] internal NetworkObject CreateLocalNetworkObject(NetworkObject.SerializedObject serializedObject, byte[] instantiationData = null) { NetworkObject networkObject = null; @@ -992,11 +990,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SerializedObject s networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, serializedObject.NetworkSceneHandle); if (networkObject == null) { - if (NetworkLog.CurrentLogLevel <= LogLevel.Error) - { - NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!"); - } - + NetworkLog.LogErrorServer(new Context(LogLevel.Error, $"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure!").AddInfo(nameof(NetworkObject.GlobalObjectIdHash), globalObjectIdHash)); return null; } @@ -1136,7 +1130,14 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n return false; } - if (!sceneObject && NetworkManager.LogLevel <= LogLevel.Error) + if (playerObject && networkObject.InScenePlaced) + { + NetworkLog.LogError(new Context(LogLevel.Developer, "Player prefab is marked as belonging to a scene. This may cause issues.").AddNetworkObject(networkObject).AddInfo("SceneName", networkObject.SceneOrigin.name)); + networkObject.InScenePlaced = false; + } + NetworkLog.InternalAssert(sceneObject == networkObject.InScenePlaced, "Legacy sceneObject value should match calculated InScenePlaced value."); + + if (!networkObject.InScenePlaced && NetworkManager.LogLevel <= LogLevel.Error) { var networkObjectChildren = networkObject.GetComponentsInChildren(); if (networkObjectChildren.Length > 1) @@ -1151,48 +1152,13 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n networkObject.NetworkManagerOwner = NetworkManager; networkObject.InvokeBehaviourNetworkPreSpawn(); - // DANGO-TODO: It would be nice to allow users to specify which clients are observers prior to spawning - // For now, this is the best place I could find to add all connected clients as observers for newly - // instantiated and spawned NetworkObjects on the authoritative side. - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManager.DistributedAuthorityMode && NetworkManager.NetworkConfig.EnableSceneManagement && networkObject.InScenePlaced) { - if (NetworkManager.NetworkConfig.EnableSceneManagement && sceneObject) - { - networkObject.SceneOriginHandle = networkObject.gameObject.scene.handle; - networkObject.NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[networkObject.gameObject.scene.handle]; - } - - // Always add the owner/authority even if SpawnWithObservers is false - // (authority should not take into consideration networkObject.CheckObjectVisibility when SpawnWithObservers is false) - if (!networkObject.SpawnWithObservers) - { - networkObject.AddObserver(ownerClientId); - } - else - { - foreach (var clientId in NetworkManager.ConnectionManager.ConnectedClientIds) - { - // If SpawnWithObservers is enabled, then authority does take networkObject.CheckObjectVisibility into consideration - if (networkObject.CheckObjectVisibility != null && !networkObject.CheckObjectVisibility.Invoke(clientId)) - { - continue; - } - networkObject.AddObserver(clientId); - } - - // Sanity check to make sure the owner is always included - // Intentionally checking as opposed to just assigning in order to generate notification. - if (!networkObject.Observers.Contains(ownerClientId)) - { - if (NetworkManager.LogLevel <= LogLevel.Error) - { - NetworkLog.LogError($"Client-{ownerClientId} is the owner of {networkObject.name} but is not an observer! Adding owner, but there is a bug in observer synchronization!"); - } - networkObject.AddObserver(ownerClientId); - } - } + networkObject.SceneOriginHandle = networkObject.gameObject.scene.handle; + networkObject.NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[networkObject.gameObject.scene.handle]; } + if (!SpawnNetworkObjectLocallyCommon(networkObject, networkId, sceneObject, playerObject, ownerClientId, destroyWithScene)) { if (NetworkManager.LogLevel <= LogLevel.Error) @@ -1224,20 +1190,17 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n /// /// Only spawn non-authority instances should invoke this. /// This is invoked to instantiate an authority spawned , and - /// is only invoked by: + /// is only invoked by: /// /// - /// IMPORTANT: Pre spawn methods need to be invoked from within . + /// IMPORTANT: Pre spawn methods need to be invoked from within . /// /// boolean indicating whether the spawn succeeded - internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serializedObject, out NetworkObject networkObject, FastBufferReader reader, bool destroyWithScene) + internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serializedObject, [MaybeNullWhen(false)] out NetworkObject networkObject, FastBufferReader reader, bool destroyWithScene) { if (SpawnedObjects.ContainsKey(serializedObject.NetworkObjectId)) { - if (NetworkManager.LogLevel <= LogLevel.Error) - { - NetworkLog.LogWarning($"Trying to spawn a {nameof(NetworkObject)} with a {nameof(NetworkObject.NetworkObjectId)} of {serializedObject.NetworkObjectId} but an object with that id is already in the spawned list. This should not happen!"); - } + NetworkManager.Log.Warning(new Context(LogLevel.Error, $"Trying to spawn a {nameof(NetworkObject)} but an object with that {nameof(NetworkObject.NetworkObjectId)} is already in the spawned list. This should not happen!")); networkObject = null; return false; } @@ -1254,14 +1217,11 @@ internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serialize // Log the error that the NetworkObject failed to construct if (networkObject == null) { - if (NetworkManager.LogLevel <= LogLevel.Normal) - { - NetworkLog.LogError($"[{nameof(NetworkObject.GlobalObjectIdHash)}={serializedObject.Hash}] Failed to spawn {nameof(NetworkObject)}!"); - } - + NetworkManager.Log.ErrorServer(new Context(LogLevel.Normal, $"Failed to spawn {nameof(NetworkObject)}!").AddInfo(nameof(NetworkObject.GlobalObjectIdHash), serializedObject.Hash)); return false; } + networkObject.InScenePlaced = serializedObject.IsSceneObject; networkObject.NetworkManagerOwner = NetworkManager; // This will get set again when the NetworkObject is spawned locally, but we set it here ahead of spawning @@ -1286,11 +1246,7 @@ internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serialize if (networkObject.IsSpawned) { - if (NetworkManager.LogLevel <= LogLevel.Error) - { - NetworkLog.LogErrorServer($"[{networkObject.name}] Object-{networkObject.NetworkObjectId} is already spawned!"); - } - + NetworkManager.Log.ErrorServer(new Context(LogLevel.Normal, $"{nameof(NetworkObject)} is already spawned!").AddNetworkObject(networkObject)); // Mark the spawn as a success if the object is already spawned return true; } @@ -1319,9 +1275,10 @@ internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serialize } /// - /// Handles the all the final setup and spawning needed for + /// Handles all the final setup and spawning needed for spawning a NetworkObject locally. /// - /// boolean indicating whether the spawn succeeded. Internal dev note: THIS IS A CATCH FOR OURSELVES. DON'T PULL OUT + /// boolean indicating whether the spawn succeeded. + // Internal dev note: THIS IS A CATCH FOR OURSELVES. DON'T PULL OUT internal bool SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene) { // TODO: Replace the following checks with internal Netcode asserts @@ -1335,7 +1292,7 @@ internal bool SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong return false; } - if (networkId == default) + if (networkId == 0) { if (NetworkManager.LogLevel <= LogLevel.Error) { @@ -1350,82 +1307,23 @@ internal bool SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkObject.SetSceneObjectStatus(sceneObject); #pragma warning restore CS0618 // Type or member is obsolete - // Always check to make sure our scene of origin is properly set for in-scene placed NetworkObjects - // Note: Always check SceneOriginHandle directly at this specific location. - if (networkObject.InScenePlaced && networkObject.SceneOriginHandle.IsEmpty()) - { - networkObject.SceneOrigin = networkObject.gameObject.scene; - } - - networkObject.NetworkObjectId = networkId; - - networkObject.DestroyWithScene = sceneObject || destroyWithScene; - - networkObject.IsPlayerObject = playerObject; - - networkObject.OwnerClientId = ownerClientId; + networkObject.SetupOnSpawn(networkId, playerObject, ownerClientId, destroyWithScene); - // When spawned, previous owner is always the first assigned owner - networkObject.PreviousOwnerId = ownerClientId; - - // If this the player and the client is the owner, then lock ownership by default - if (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClientId == ownerClientId && playerObject) - { - networkObject.AddOwnershipExtended(NetworkObject.OwnershipStatusExtended.Locked); - } - - networkObject.IsSpawned = true; SpawnedObjects.Add(networkObject.NetworkObjectId, networkObject); SpawnedObjectsList.Add(networkObject); - - // If we are not running in DA mode, this is the server, and the NetworkObject has SpawnWithObservers set, - // then add all connected clients as observers - if (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer && networkObject.SpawnWithObservers) - { - // If running as a server only, then make sure to always add the server's client identifier - if (!NetworkManager.IsHost) - { - networkObject.AddObserver(NetworkManager.LocalClientId); - } - - // Add client observers - for (int i = 0; i < NetworkManager.ConnectedClientsIds.Count; i++) - { - // If CheckObjectVisibility has a callback, then allow that method determine who the observers are. - if (networkObject.CheckObjectVisibility != null && !networkObject.CheckObjectVisibility(NetworkManager.ConnectedClientsIds[i])) - { - continue; - } - networkObject.AddObserver(NetworkManager.ConnectedClientsIds[i]); - } - } - networkObject.ApplyNetworkParenting(); + NetworkObject.CheckOrphanChildren(); AddNetworkObjectToSceneChangedUpdates(networkObject); - + UpdateOwnershipTable(networkObject, ownerClientId); networkObject.InvokeBehaviourNetworkSpawn(); - - // Only dynamically spawned NetworkObjects are allowed - if (!sceneObject) - { - networkObject.SubscribeToActiveSceneForSynch(); - } - if (networkObject.IsPlayerObject) { UpdateNetworkClientPlayer(networkObject); } - // If we are an in-scene placed NetworkObject and our InScenePlacedSourceGlobalObjectIdHash is set - // then assign this to the PrefabGlobalObjectIdHash - if (networkObject.InScenePlaced && networkObject.InScenePlacedSourceGlobalObjectIdHash != 0) - { - networkObject.PrefabGlobalObjectIdHash = networkObject.InScenePlacedSourceGlobalObjectIdHash; - } - return true; } @@ -1485,7 +1383,7 @@ internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject } var message = new CreateObjectMessage { - ObjectInfo = networkObject.Serialize(clientId, NetworkManager.DistributedAuthorityMode), + ObjectInfo = networkObject.SerializeSpawnedObject(clientId, NetworkManager.DistributedAuthorityMode), IncludesSerializedObject = true, UpdateObservers = NetworkManager.DistributedAuthorityMode, ObserverIds = NetworkManager.DistributedAuthorityMode ? networkObject.Observers.ToArray() : null, @@ -1511,7 +1409,7 @@ internal void SendSpawnCallForObserverUpdate(ulong[] newObservers, NetworkObject var message = new CreateObjectMessage { - ObjectInfo = networkObject.Serialize(), + ObjectInfo = networkObject.SerializeSpawnedObject(), ObserverIds = networkObject.Observers.ToArray(), NewObserverIds = newObservers.ToArray(), IncludesSerializedObject = true, @@ -1572,14 +1470,13 @@ internal void DespawnObject(NetworkObject networkObject, bool destroyObject = fa internal void ServerResetShutdownStateForSceneObjects() { var networkObjects = FindObjects.ByType(orderByIdentifier: true, includeInactive: true); - foreach (var sobj in networkObjects) + foreach (var obj in networkObjects) { - if (!sobj.InScenePlaced) + // Only reset things that have been spawned are in-scene placed, and is assigned to the relative NetworkManager (for integration testing purposes) + if (obj.HasBeenSpawned && obj.InScenePlaced && obj.NetworkManagerOwner == NetworkManager) { - continue; + obj.ResetOnShutdown(); } - sobj.IsSpawned = false; - sobj.DestroyWithScene = false; } } @@ -1852,8 +1749,7 @@ internal void OnDespawnObject([NotNull] NetworkObject networkObject, bool destro NetworkLog.LogError($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} could not be moved to the root when its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} was being destroyed"); } } - else - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + else if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed"); } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples.meta b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples.meta new file mode 100644 index 0000000000..bc0cce73b4 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0f959eedfa8244038f30beb1f00b8cf0 +timeCreated: 1780100790 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md new file mode 100644 index 0000000000..33ca58ffcc --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md @@ -0,0 +1,31 @@ +# Code documentation + +Use this folder for any code snippets that only need to compile. + +Any code snippets that you want to run tests on should be put in [Runtime tests](../../Runtime/Documentation/README.md). + +To embed code in documentation, use the following tag + +```md +[!code-cs[](../../Tests/Editor/DocumentationCodeSamples/.cs#SomeRegionName)] +``` + +With the code formatted like this + +```cs +namespace DocumentationCodeSamples +{ + internal MyTestClass + { + #region SomeRegionName + // All the code in this region block will be embedded without indentation in the docs. + #endregion + + [Test] + public void TestOfDocumentationCode() + { + ... + } + } +} +``` diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md.meta b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md.meta new file mode 100644 index 0000000000..d43f352993 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a6afff0520856465a9f94b9a5bcebf24 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization.meta b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization.meta new file mode 100644 index 0000000000..677d24fb22 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9371809eac694274ad78a6e7faf27e69 +timeCreated: 1780674489 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs new file mode 100644 index 0000000000..3f70a15cb7 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs @@ -0,0 +1,114 @@ +using NUnit.Framework; +using Unity.Collections; +using Unity.Netcode; + +namespace DocumentationCodeSamples +{ + #region HealthStruct + /// Container for storing health data for a player or item. + public struct Health + { + /// + /// The maximum health that this player or item can have. + /// This is unlikely to change often. + /// + public uint MaxHealth; + + /// + /// The current level of health that this player or item has. + /// This is likely to change regularly. + /// + public int CurrentHealth; + } + #endregion + + #region FastBuffer + /// Tells the Netcode how to serialize and deserialize our custom type. + // The class name doesn't matter here. + public static class FastBufferExtensions + { + /// + /// Extension method to override the serialization for a custom type. + /// + /// Buffer to write values into. + /// The type to customize or override. + public static void WriteValueSafe(this FastBufferWriter writer, in Health health) + { + writer.WriteValueSafe(health.MaxHealth); + writer.WriteValueSafe(health.CurrentHealth); + } + + /// + /// Extension method to override the de-serialization for a custom type. + /// + /// Buffer to read values from. + /// The type to customize or override. + public static void ReadValueSafe(this FastBufferReader reader, out Health health) + { + reader.ReadValueSafe(out uint max); + reader.ReadValueSafe(out int current); + health = new Health { MaxHealth = max, CurrentHealth = current }; + } + } + #endregion + + #region BufferSerializer + /// Tells the how to serialize and deserialize our custom type. + // The class name doesn't matter here. + public static class BufferSerializerExtensions + { + /// + /// Extension method to override bi-directional serialization for a custom type. + /// + /// Bi-directional serial + /// The type to customize or override. + /// Boilerplate syntax to enable the bi-directional serialization. + public static void SerializeValue(this BufferSerializer serializer, ref Health health) where TReaderWriter : IReaderWriter + { + // Because the BufferSerializer already knows how to read and write the primitive types + // We can use the existing BufferSerializer serialization. + serializer.SerializeValue(ref health.MaxHealth); + serializer.SerializeValue(ref health.CurrentHealth); + } + } + #endregion + + internal class TestSerializationDocs + { + [Test] + public void TestFastBufferSerialization() + { + var healthToTest = new Health { MaxHealth = 123, CurrentHealth = 89 }; + var expected = healthToTest; + + using var writer = new FastBufferWriter(256, Allocator.Temp, int.MaxValue); + writer.WriteValueSafe(healthToTest); + + using var reader = new FastBufferReader(writer, Allocator.None); + reader.ReadValueSafe(out Health readHealth); + + Assert.AreEqual(expected.MaxHealth, readHealth.MaxHealth); + Assert.AreEqual(expected.CurrentHealth, readHealth.CurrentHealth); + } + + [Test] + public void TestBufferSerializerSerialization() + { + var healthToTest = new Health { MaxHealth = 456, CurrentHealth = 78 }; + var expected = healthToTest; + + using var writer = new FastBufferWriter(256, Allocator.Temp, int.MaxValue); + var bufferWriter = new BufferSerializer(new BufferSerializerWriter(writer)); + bufferWriter.SerializeValue(ref healthToTest); + + using var tempReader = new FastBufferReader(bufferWriter.GetFastBufferWriter(), Allocator.None); + var bufferReader = new BufferSerializer(new BufferSerializerReader(tempReader)); + var readHealth = new Health(); + bufferReader.SerializeValue(ref readHealth); + + Assert.AreEqual(expected.MaxHealth, readHealth.MaxHealth); + Assert.AreEqual(expected.CurrentHealth, readHealth.CurrentHealth); + + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs.meta new file mode 100644 index 0000000000..a39141e43d --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 08949d7abc1249418192e44999279f89 +timeCreated: 1780101010 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs index 3c955f39aa..b27eda622b 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs @@ -924,6 +924,50 @@ public void GivenFastBufferWriterContainingValue_WhenReadingString_ValueMatchesW } } + /// + /// This validates that catches a potential + /// scenario where the string's character count value has already been read + /// due to an error within user script and the resultant character count + /// multiplied times 2 (when using 2 bytes vs 1) causes the length to roll over + /// to a negative value which, in turn, causes the reader to attempt to read + /// into restricted memory and causes the editor to crash. + /// + [Test] + public void ReadingStringAfterStringLengthHasAlreadyBeenRead([Values] bool isSafeRead) + { + // This was an issue uncovered in UUM-145752 that resulted + // in the below text to result in a length that when using + // 2 bytes per character would cause the skewed size to roll + // over into a negative value causing the editor to crash + // when it attempted to read a large negative offset value. + string valueToTest = "true"; + + var serializedValueSize = FastBufferWriter.GetWriteSize(valueToTest); + + using var writer = new FastBufferWriter(serializedValueSize, Allocator.Temp); + writer.WriteValueSafe(valueToTest); + + using var reader = new FastBufferReader(writer, Allocator.Temp); + + // Read the value of the character count before trying to read the string + // This mocks user code having read too far into a stream causing the position to be skewed such that + // the string reader reads the some of the bytes for the actual text as the length. + reader.ReadByteSafe(out byte count); + Assert.True(count == valueToTest.Length, $"Count ({count}) is not the expected size of {valueToTest.Length}!"); + if (isSafeRead) + { + // This should throw an overflow exception but should not crash the editor. + Assert.Throws(() => reader.ReadValueSafe(out string valueRead)); + } + else + { + // Assume user does a pre-calculation of the size to be read: + Assert.IsTrue(reader.TryBeginRead(count), "Reader denied read permission"); + + // This should throw an overflow exception but should not crash the editor. + Assert.Throws(() => reader.ReadValue(out string valueRead)); + } + } [TestCase(1, 0)] [TestCase(2, 0)] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs index b6157324bc..fb88b9ada8 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs @@ -76,11 +76,6 @@ internal class TestNetworkComponent : NetworkBehaviour { public NetworkList MyNetworkList = new NetworkList(new List { 1, 2, 3 }); public NetworkVariable MyNetworkVar = new NetworkVariable(3); - - [Rpc(SendTo.Authority)] - public void TestAuthorityRpc(byte[] _) - { - } } protected override void OnOneTimeSetup() @@ -157,62 +152,6 @@ protected override IEnumerator OnStartedServerAndClients() m_Client.MessageManager.Hook(m_ClientCodecHook); } - [UnityTest] - public IEnumerator AuthorityRpc() - { - var player = m_Client.LocalClient.PlayerObject; - player.OwnerClientId = m_Client.LocalClientId + 1; - - var networkComponent = player.GetComponent(); - networkComponent.UpdateNetworkProperties(); - networkComponent.TestAuthorityRpc(new byte[] { 1, 2, 3, 4 }); - - // Universal Rpcs are sent as a ProxyMessage (which contains an RpcMessage) - yield return m_ClientCodecHook.WaitForMessageReceived(); - } - - [UnityTest] - public IEnumerator ChangeOwnership() - { - var message = new ChangeOwnershipMessage - { - DistributedAuthorityMode = true, - NetworkObjectId = 100, - OwnerClientId = 2, - }; - - yield return SendMessage(ref message); - } - - [UnityTest] - public IEnumerator ClientConnected() - { - var message = new ClientConnectedMessage() - { - ClientId = 2, - }; - - yield return SendMessage(ref message); - } - - [UnityTest] - public IEnumerator ClientDisconnected() - { - var message = new ClientDisconnectedMessage() - { - ClientId = 2, - }; - - yield return SendMessage(ref message); - } - - [UnityTest] - public IEnumerator CreateObject() - { - SpawnObject(m_SpawnObject, m_Client); - yield return m_ClientCodecHook.WaitForMessageReceived(); - } - [UnityTest] public IEnumerator DestroyObject() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs index 5697833bbc..929592a9b0 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs @@ -246,8 +246,7 @@ private bool ValidateAllPlayerObjects() m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkClient.PlayerObject)} is null!"); success = false; } - else - if (!client.SpawnManager.PlayerObjects.Contains(client.LocalClient.PlayerObject)) + else if (!client.SpawnManager.PlayerObjects.Contains(client.LocalClient.PlayerObject)) { m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkSpawnManager.PlayerObjects)} does not contain {client.LocalClient.PlayerObject.name}!"); success = false; diff --git a/testproject/Assets/Tests/Runtime/Physics.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples.meta similarity index 77% rename from testproject/Assets/Tests/Runtime/Physics.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples.meta index de0808f1a7..aea9d686c3 100644 --- a/testproject/Assets/Tests/Runtime/Physics.meta +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: efc90b4d4d76f7a48ad1cf726a3a193e +guid: e017ce40eb7744e03bc0a83fa18ddcc3 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration.meta new file mode 100644 index 0000000000..baf6e6f199 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ac415df2337f69349996d9da97f07242 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs new file mode 100644 index 0000000000..945807a06c --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs @@ -0,0 +1,220 @@ +using System.Collections; +using System.Text; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using Unity.Netcode.Transports.SinglePlayer; +using Unity.Netcode.Transports.UTP; +using UnityEngine; +using UnityEngine.TestTools; + +namespace DocumentationCodeSamples +{ + #region SinglePlayerTransportExample + /// + /// Example of how to start a single player or multiplayer session. + /// Place this on your NetworkManager's GameObject. + /// + internal class SwitchingTransportTypesExample : MonoBehaviour + { + public enum StartType + { + SinglePlayer, + Client, + Host, + Server + } + + private UnityTransport m_UnityTransport; + private SinglePlayerTransport m_SinglePlayerTransport; + private NetworkManager m_NetworkManager; + + private void Awake() + { + m_UnityTransport = GetComponent(); + m_SinglePlayerTransport = GetComponent(); + m_NetworkManager = GetComponent(); + } + + public bool StartSession(StartType startType) + { + var startStatus = false; + // Set the transport to use before starting + m_NetworkManager.NetworkConfig.NetworkTransport = startType == StartType.SinglePlayer ? m_SinglePlayerTransport : m_UnityTransport; + switch (startType) + { + case StartType.Host: + case StartType.SinglePlayer: + { + // Starting a host or single player is the same + startStatus = m_NetworkManager.StartHost(); + break; + } + case StartType.Server: + { + startStatus = m_NetworkManager.StartServer(); + break; + } + case StartType.Client: + { + startStatus = m_NetworkManager.StartClient(); + break; + } + } + return startStatus; + } + } + #endregion + + internal class VerifyNetcodeSessionActive : NetworkBehaviour + { + public bool RpcReceived { get; private set; } + + public bool TestNetworkVariableEvent { get; private set; } + public NetworkVariable TestNetworkVariable = new NetworkVariable(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + + [Rpc(SendTo.Everyone, InvokePermission = RpcInvokePermission.Owner)] + private void VerifyRpc(RpcParams rpcParams = default) + { + RpcReceived = true; + } + + public void TestConnectivity() + { + if (IsOwner) + { + VerifyRpc(); + TestNetworkVariable.Value = true; + } + } + + protected override void OnNetworkPostSpawn() + { + TestNetworkVariable.OnValueChanged += TestNetworkVariableChanged; + base.OnNetworkPostSpawn(); + } + + private void TestNetworkVariableChanged(bool previous, bool current) + { + TestNetworkVariableEvent = true; + } + } + + [TestFixture(SessionType.SinglePlayer)] + [TestFixture(SessionType.MultiPlayer)] + internal class SwitchingTransportTypesTests : NetcodeIntegrationTest + { + public enum SessionType + { + SinglePlayer, + MultiPlayer + } + + protected override int NumberOfClients => 0; + + // We do not need to test this against CMB. + protected override bool UseCMBService() + { + return false; + } + + private SessionType m_SessionType; + public SwitchingTransportTypesTests(SessionType sessionType) + { + m_SessionType = sessionType; + } + + protected override IEnumerator OnSetup() + { + m_CanStart = false; + return base.OnSetup(); + } + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + base.OnCreatePlayerPrefab(); + } + + private bool m_CanStart = false; + protected override bool CanStartServerAndClients() + { + return m_CanStart; + } + + private NetworkManager m_Client; + protected override void OnNewClientCreated(NetworkManager networkManager) + { + m_Client = networkManager; + networkManager.NetworkConfig.EnableSceneManagement = false; + base.OnNewClientCreated(networkManager); + } + + [UnityTest] + public IEnumerator SwitchTransportTest() + { + var authority = GetAuthorityNetworkManager(); + authority.NetworkConfig.EnableSceneManagement = false; + var startType = SwitchingTransportTypesExample.StartType.Host; + if (m_SessionType == SessionType.SinglePlayer) + { + startType = SwitchingTransportTypesExample.StartType.SinglePlayer; + authority.gameObject.AddComponent(); + } + m_CanStart = true; + var example = authority.gameObject.AddComponent(); + Assert.IsTrue(example.StartSession(startType), "Failed to start single player session!"); + + if (m_SessionType != SessionType.SinglePlayer) + { + yield return CreateAndStartNewClient(); + AssertOnTimeout("Timed out waiting for client to start and connect!"); + } + + var verifyNetcode = authority.LocalClient.PlayerObject.GetComponent(); + verifyNetcode.TestConnectivity(); + if (m_SessionType != SessionType.SinglePlayer) + { + m_Client.LocalClient.PlayerObject.GetComponent().TestConnectivity(); + } + + yield return WaitForConditionOrTimeOut(VerifyNetcodeSession); + AssertOnTimeout($"Single player session had netcode related errors:"); + } + + /// + /// Verifies that all spawned players received the RPC and NetworkVariable + /// changed event. + /// + private bool VerifyNetcodeSession(StringBuilder errorLog) + { + foreach (var networkManager in m_NetworkManagers) + { + foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList) + { + if (!networkObject.IsPlayerObject) + { + continue; + } + var verify = networkObject.gameObject.GetComponent(); + + if (!verify.RpcReceived) + { + errorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{verify.name}] Rpc was not recieved!"); + } + + if (!verify.TestNetworkVariableEvent) + { + errorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{verify.name}] NetworkVariable.OnValueChanged did not get invoked!"); + } + + if (!verify.TestNetworkVariable.Value) + { + errorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{verify.name}] {nameof(VerifyNetcodeSessionActive.TestNetworkVariable)} did not get set!"); + } + } + } + return errorLog.Length == 0; + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs.meta new file mode 100644 index 0000000000..bd9ea061b2 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cd8ec5118fc47e449ae915c51a3806b4 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable.meta new file mode 100644 index 0000000000..6fc7d1074d --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 446ef307887e3446f896c899f43af442 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs new file mode 100644 index 0000000000..74fb7fe157 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace DocumentationCodeSamples +{ + #region TestMyCustomNetworkVariable + // Using MyCustomNetworkVariable within a NetworkBehaviour + internal class TestMyCustomNetworkVariable : NetworkBehaviour + { + public MyCustomNetworkVariable CustomNetworkVariable = new MyCustomNetworkVariable(); + public MyCustomGenericNetworkVariable CustomGenericNetworkVariable = new MyCustomGenericNetworkVariable(); + + public override void OnNetworkSpawn() + { + if (IsServer) + { + for (int i = 0; i < 4; i++) + { + var someData = new SomeData + { + SomeFloatData = i, + SomeIntData = i + }; + someData.SomeListOfValues.Add((ulong)i + 1000000); + someData.SomeListOfValues.Add((ulong)i + 2000000); + someData.SomeListOfValues.Add((ulong)i + 3000000); + CustomNetworkVariable.SomeDataToSynchronize.Add(someData); + CustomNetworkVariable.SetDirty(true); + + CustomGenericNetworkVariable.SomeDataToSynchronize.Add(i); + CustomGenericNetworkVariable.SetDirty(true); + } + } + } + } + + /// + /// Bare minimum example of NetworkVariableBase derived class + /// + [Serializable] + public class MyCustomNetworkVariable : NetworkVariableBase + { + /// + /// Managed list of class instances + /// + internal List SomeDataToSynchronize = new List(); + + /// + /// Writes the complete state of the variable to the writer + /// + /// The stream to write the state to + public override void WriteField(FastBufferWriter writer) + { + // Serialize the data we need to synchronize + writer.WriteValueSafe(SomeDataToSynchronize.Count); + foreach (var dataEntry in SomeDataToSynchronize) + { + writer.WriteValueSafe(dataEntry.SomeIntData); + writer.WriteValueSafe(dataEntry.SomeFloatData); + writer.WriteValueSafe(dataEntry.SomeListOfValues.Count); + foreach (var valueItem in dataEntry.SomeListOfValues) + { + writer.WriteValueSafe(valueItem); + } + } + } + + /// + /// Reads the complete state from the reader and applies it + /// + /// The stream to read the state from + public override void ReadField(FastBufferReader reader) + { + // De-Serialize the data being synchronized + var itemsToUpdate = 0; + reader.ReadValueSafe(out itemsToUpdate); + SomeDataToSynchronize.Clear(); + for (int i = 0; i < itemsToUpdate; i++) + { + var newEntry = new SomeData(); + reader.ReadValueSafe(out newEntry.SomeIntData); + reader.ReadValueSafe(out newEntry.SomeFloatData); + var itemsCount = 0; + var tempValue = (ulong)0; + reader.ReadValueSafe(out itemsCount); + newEntry.SomeListOfValues.Clear(); + for (int j = 0; j < itemsCount; j++) + { + reader.ReadValueSafe(out tempValue); + newEntry.SomeListOfValues.Add(tempValue); + } + + SomeDataToSynchronize.Add(newEntry); + } + } + + /// + /// Used to write partial updates rather than synchronizing the full state on every change. + /// + /// The stream to write the state to + public override void WriteDelta(FastBufferWriter writer) + { + // Not implemented for this example, instead we can write the field + WriteField(writer); + } + + /// + /// Used to read partial updates rather than synchronizing the full state on every change. + /// + /// + public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) + { + // Not implemented for this example, instead we can read the field + ReadField(reader); + } + + } + + /// + /// Bare minimum example of generic NetworkVariableBase derived class + /// + /// Generic type marker + [Serializable] + [GenerateSerializationForGenericParameter(0)] + public class MyCustomGenericNetworkVariable : NetworkVariableBase + { + /// Managed list of class instances + public List SomeDataToSynchronize = new List(); + + /// + /// Writes the complete state of the variable to the writer + /// + /// The stream to write the state to + public override void WriteField(FastBufferWriter writer) + { + // Serialize the data we need to synchronize + writer.WriteValueSafe(SomeDataToSynchronize.Count); + for (var i = 0; i < SomeDataToSynchronize.Count; ++i) + { + var dataEntry = SomeDataToSynchronize[i]; + // NetworkVariableSerialization is used for serializing generic types + NetworkVariableSerialization.Write(writer, ref dataEntry); + } + } + + /// + /// Reads the complete state from the reader and applies it + /// + /// The stream to read the state from + public override void ReadField(FastBufferReader reader) + { + // De-Serialize the data being synchronized + var itemsToUpdate = 0; + reader.ReadValueSafe(out itemsToUpdate); + SomeDataToSynchronize.Clear(); + for (int i = 0; i < itemsToUpdate; i++) + { + T newEntry = default; + // NetworkVariableSerialization is used for serializing generic types + NetworkVariableSerialization.Read(reader, ref newEntry); + SomeDataToSynchronize.Add(newEntry); + } + } + + /// + /// Used to write partial updates rather than synchronizing the full state on every change. + /// + /// The stream to write the state to + public override void WriteDelta(FastBufferWriter writer) + { + // Not implemented for this example, instead we can write the field + WriteField(writer); + } + + /// + /// Used to read partial updates rather than synchronizing the full state on every change. + /// + /// + public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) + { + // Not implemented for this example, instead we can read the field + ReadField(reader); + } + } + + [Serializable] + internal class SomeData + { + public int SomeIntData = default; + public float SomeFloatData = default; + public List SomeListOfValues = new List(); + } + #endregion + + + internal class CustomNetworkVariableTest : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + private GameObject m_PrefabToSpawn; + + protected override void OnServerAndClientsCreated() + { + m_PrefabToSpawn = CreateNetworkObjectPrefab(nameof(TestMyCustomNetworkVariable)); + m_PrefabToSpawn.AddComponent(); + } + + /// + /// Validates when the authority applies a value during spawn or + /// post spawn of a newly instantiated and spawned object the value is set by the time non-authority + /// instances invoke . + /// + [UnityTest] + public IEnumerator CustomNetworkVariableCodeWorks() + { + var authority = GetAuthorityNetworkManager(); + var authorityObject = SpawnObject(m_PrefabToSpawn, authority).GetComponent(); + var authorityBehaviour = authorityObject.GetComponent(); + + yield return WaitForSpawnedOnAllOrTimeOut(authorityObject.NetworkObjectId); + AssertOnTimeout("Failed to spawn network object"); + + foreach (var networkManager in m_NetworkManagers) + { + Assert.True(networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityObject.NetworkObjectId, out var localObject)); + var testBehaviour = localObject.GetComponent(); + Assert.NotNull(testBehaviour); + Assert.AreEqual(authorityBehaviour.CustomNetworkVariable.SomeDataToSynchronize.Count, testBehaviour.CustomNetworkVariable.SomeDataToSynchronize.Count, $"[Client-{networkManager.LocalClientId}] Incorrect length found for {nameof(MyCustomNetworkVariable)}"); + Assert.AreEqual(authorityBehaviour.CustomGenericNetworkVariable.SomeDataToSynchronize, testBehaviour.CustomGenericNetworkVariable.SomeDataToSynchronize, $"[Client-{networkManager.LocalClientId}] Incorrect length found for {nameof(MyCustomNetworkVariable)}"); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs.meta new file mode 100644 index 0000000000..5f186a3e56 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a4617887a8b0d46be9babcc146fcfd20 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs new file mode 100644 index 0000000000..1e6096b187 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs @@ -0,0 +1,213 @@ +using System.Collections; +using System.Text; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace DocumentationCodeSamples +{ + internal static class FastBufferExtensions + { + internal static void WriteValueSafe(this FastBufferWriter writer, in CustomSerializationDocsTests.Health health) + { + writer.WriteValueSafe(health.MaxHealth); + writer.WriteValueSafe(health.CurrentHealth); + } + + internal static void ReadValueSafe(this FastBufferReader reader, out CustomSerializationDocsTests.Health health) + { + reader.ReadValueSafe(out uint max); + reader.ReadValueSafe(out int current); + health = new CustomSerializationDocsTests.Health { MaxHealth = max, CurrentHealth = current }; + } + } + + + internal class CustomSerializationDocsTests : NetcodeIntegrationTest + { + #region HealthExample + public struct Health + { + public uint MaxHealth; + public int CurrentHealth; + + // Register our custom serialization on load + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + public static void RegisterHealthSerialization() + { + // You can reuse the FastBufferWriter and FastBufferReader extension methods we wrote above + UserNetworkVariableSerialization.WriteValue = FastBufferExtensions.WriteValueSafe; + UserNetworkVariableSerialization.ReadValue = FastBufferExtensions.ReadValueSafe; + + // Here is where you register your custom delta handling. + UserNetworkVariableSerialization.WriteDelta = WriteDelta; + UserNetworkVariableSerialization.ReadDelta = ReadDelta; + + // You can also use lambda expressions to register functions + UserNetworkVariableSerialization.DuplicateValue = (in Health value, ref Health duplicatedValue) => { duplicatedValue = value; }; + } + + // We can use an enum to indicate which field has changed for the delta change. + // This lets us save bandwidth when only one value has changed. + // In the case of Health, we expect CurrentHealth to change much more often than MaxHealth + // Implementing WriteDelta saves us bandwidth on not sending the MaxHealth every time CurrentHealth changes. + private enum ChangeType : byte + { + MaxHealth, + CurrentHealth, + All, + } + + public static void WriteDelta(FastBufferWriter writer, in Health value, in Health previousValue) + { + if (value.MaxHealth == previousValue.MaxHealth && value.CurrentHealth != previousValue.CurrentHealth) + { + // If only our CurrentHealth has changed, we can send the CurrentHealth enum with only the updated CurrentHealth value + writer.WriteValueSafe(ChangeType.CurrentHealth); + writer.WriteValueSafe(value.CurrentHealth); + } + else if (value.CurrentHealth == previousValue.CurrentHealth && value.MaxHealth != previousValue.MaxHealth) + { + // If only our MaxHealth has changed, we can send the MaxHealth enum with only the updated MaxHealth value + writer.WriteValueSafe(ChangeType.MaxHealth); + writer.WriteValueSafe(value.MaxHealth); + } + else + { + // If both values have changed, we need to serialize both values. + writer.WriteValueSafe(ChangeType.All); + writer.WriteValueSafe(value.MaxHealth); + writer.WriteValueSafe(value.CurrentHealth); + } + } + + public static void ReadDelta(FastBufferReader reader, ref Health value) + { + // First we read what type of change we've received + reader.ReadValueSafe(out ChangeType changeType); + + // Then we read the data in our delta message, based on what type of change we've received. + switch (changeType) + { + case ChangeType.CurrentHealth: + { + reader.ReadValueSafe(out value.CurrentHealth); + break; + } + case ChangeType.MaxHealth: + { + reader.ReadValueSafe(out value.MaxHealth); + break; + } + case ChangeType.All: + { + reader.ReadValueSafe(out value.MaxHealth); + reader.ReadValueSafe(out value.CurrentHealth); + break; + } + } + } + } + #endregion + + internal class TestHealthBehaviour : NetworkBehaviour + { + internal readonly NetworkVariable HealthVar = new(); + + internal Health ReceivedFromRpc; + + [Rpc(SendTo.Everyone)] + public void SendHealthRpc(Health health) + { + ReceivedFromRpc = health; + } + } + + protected override int NumberOfClients => 1; + private GameObject m_PrefabToSpawn; + + protected override void OnServerAndClientsCreated() + { + m_PrefabToSpawn = CreateNetworkObjectPrefab(nameof(TestHealthBehaviour)); + m_PrefabToSpawn.AddComponent(); + } + + private Health m_ExpectedHealth; + private ulong m_NetworkObjectIdToTest; + + private bool m_TestingRpc; + + private bool ValidateAllAreEqual(StringBuilder errorLog) + { + foreach (var networkManager in m_NetworkManagers) + { + if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(m_NetworkObjectIdToTest, out NetworkObject localInstance)) + { + errorLog.Append($"[Client-{networkManager.LocalClientId}] SpawnedObject not found!"); + return false; + } + + var healthInstance = localInstance.GetComponent(); + if (healthInstance == null) + { + errorLog.Append($"[Client-{networkManager.LocalClientId}] Health instance is null!"); + return false; + } + + var received = m_TestingRpc ? healthInstance.ReceivedFromRpc : healthInstance.HealthVar.Value; + if (m_ExpectedHealth.MaxHealth != received.MaxHealth) + { + errorLog.Append($"[Client-{networkManager.LocalClientId}] MaxHealth values don't match! Expected {m_ExpectedHealth.MaxHealth}, Received {received.MaxHealth}"); + return false; + } + + if (m_ExpectedHealth.CurrentHealth != received.CurrentHealth) + { + errorLog.Append($"[Client-{networkManager.LocalClientId}] CurrentHealth values don't match! Expected {m_ExpectedHealth.CurrentHealth}, Received {received.CurrentHealth}"); + return false; + } + } + + return true; + } + + [UnityTest] + public IEnumerator TestHealthCode() + { + var authority = GetAuthorityNetworkManager(); + var authorityInstance = SpawnObject(m_PrefabToSpawn, authority).GetComponent(); + m_NetworkObjectIdToTest = authorityInstance.NetworkObjectId; + + yield return WaitForSpawnedOnAllOrTimeOut(authorityInstance.NetworkObjectId); + AssertOnTimeout("Failed to spawn network object"); + + var healthToTest = new Health { MaxHealth = 456, CurrentHealth = 23 }; + m_ExpectedHealth = healthToTest; + m_TestingRpc = true; + + authorityInstance.SendHealthRpc(healthToTest); + + yield return WaitForConditionOrTimeOut(ValidateAllAreEqual); + AssertOnTimeout("RPC send failed"); + + m_TestingRpc = false; + healthToTest = new Health { MaxHealth = 123, CurrentHealth = 45 }; + m_ExpectedHealth = healthToTest; + + authorityInstance.HealthVar.Value = healthToTest; + + yield return WaitForConditionOrTimeOut(ValidateAllAreEqual); + AssertOnTimeout("NetworkVariable assignment failed"); + + var current = authorityInstance.HealthVar.Value; + current.CurrentHealth -= 10; + authorityInstance.HealthVar.Value = current; + + m_ExpectedHealth = current; + + yield return WaitForConditionOrTimeOut(ValidateAllAreEqual); + AssertOnTimeout("NetworkVariable update failed"); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs.meta new file mode 100644 index 0000000000..1588a4d111 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 22820b0916e341ddbcadc50030dc5f60 +timeCreated: 1780100433 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md new file mode 100644 index 0000000000..92cd488e8e --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md @@ -0,0 +1,33 @@ +# Code documentation + +Use this folder for any code examples that we want to test to ensure the runtime functionality isn't broken + +Any code snippets that are small enough that you only want to check that they compile should be in [Editor tests](../../Editor/Documentation/README.md). + +To embed code in documentation, use the following tag + +```md +[!code-cs[](../../Tests/Runtime/DocumentationCodeSamples/.cs#SomeRegionName)] +``` + +With the code formatted like this + +```cs +namespace DocumentationCodeSamples +{ + internal MyTestClass : NetcodeIntegrationTest + { + #region SomeRegionName + // All the code in this region block will be embedded without indentation in the docs. + #endregion + + protected override int NumberOfClients => 1; + + [UnityTest] + public IEnumerator TestOfDocumentationCode() + { + ... + } + } +} +``` diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md.meta new file mode 100644 index 0000000000..4f45c6f105 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8a8cfc9d633474efb95052169e9fcb50 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs index 0e226ec115..f27f28d53f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -193,7 +194,7 @@ private bool HaveLogsBeenReceived() return false; } - if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode] [SenderId:{m_ClientNetworkManagers[0].LocalClientId}] [Invalid Destroy][{m_ClientPlayerName}][NetworkObjectId:{m_ClientNetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.")) + if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, new Regex($"SenderId:{m_ClientNetworkManagers[0].LocalClientId}]"))) { return false; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs index 0cdb31c11e..5b5583f31d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs @@ -341,7 +341,17 @@ bool HasConditionBeenMet() Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out while waiting for client side despawns!"); //----------- step 2 check spawn and destroy again - authorityInstance.GetComponent().Spawn(); + var authorityObject = authorityInstance.GetComponent(); + authorityObject.Spawn(); + + // This validates invoking GetSceneOriginHandle will not throw an exception for a dynamically spawned NetworkObject + // when the scene of origin hasn't been set. + var sceneOriginHandle = authorityObject.GetSceneOriginHandle(); + + // This validates that GetSceneOriginHandle will return the GameObject's scene handle that should be the currently active scene + var activeSceneHandle = SceneManager.GetActiveScene().handle; + Assert.IsTrue(sceneOriginHandle == activeSceneHandle, $"{nameof(NetworkObject)} should have returned the active scene handle of {activeSceneHandle} but returned {sceneOriginHandle}"); + // wait a tick yield return s_DefaultWaitForTick; // check spawned again on server this is 2 because we are reusing the object which was already spawned once. @@ -366,22 +376,6 @@ bool HasConditionBeenMet() Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out while waiting for client side despawns! (2nd pass)"); } - [Test] - public void DynamicallySpawnedNoSceneOriginException() - { - var gameObject = new GameObject(); - var networkObject = gameObject.AddComponent(); - networkObject.IsSpawned = true; - networkObject.SceneOriginHandle = default; - // This validates invoking GetSceneOriginHandle will not throw an exception for a dynamically spawned NetworkObject - // when the scene of origin hasn't been set. - var sceneOriginHandle = networkObject.GetSceneOriginHandle(); - - // This validates that GetSceneOriginHandle will return the GameObject's scene handle that should be the currently active scene - var activeSceneHandle = SceneManager.GetActiveScene().handle; - Assert.IsTrue(sceneOriginHandle == activeSceneHandle, $"{nameof(NetworkObject)} should have returned the active scene handle of {activeSceneHandle} but returned {sceneOriginHandle}"); - } - private class TrackOnSpawnFunctions : NetworkBehaviour { public int OnNetworkSpawnCalledCount { get; private set; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs index 6224c6c627..7535e4ffce 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs @@ -8,7 +8,6 @@ namespace Unity.Netcode.RuntimeTests { - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-14172 [TestFixture(VariableLengthSafety.DisableNetVarSafety, HostOrServer.DAHost)] [TestFixture(VariableLengthSafety.DisableNetVarSafety, HostOrServer.Host)] @@ -28,12 +27,6 @@ internal class NetworkObjectSynchronizationTests : NetcodeIntegrationTest private LogLevel m_CurrentLogLevel; - // TODO: [CmbServiceTests] Adapt to run with the service - protected override bool UseCMBService() - { - return false; - } - public enum VariableLengthSafety { DisableNetVarSafety, @@ -57,15 +50,15 @@ protected override void OnCreatePlayerPrefab() protected override void OnServerAndClientsCreated() { - + var authority = GetAuthorityNetworkManager(); // Set the NetworkVariable Safety Check setting - m_ServerNetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety = m_VariableLengthSafety == VariableLengthSafety.EnabledNetVarSafety; + authority.NetworkConfig.EnsureNetworkVariableLengthSafety = m_VariableLengthSafety == VariableLengthSafety.EnabledNetVarSafety; // Ignore the errors generated during this test (they are expected) - m_ServerNetworkManager.LogLevel = LogLevel.Nothing; + authority.LogLevel = LogLevel.Nothing; // Disable forcing the same prefabs to avoid failed connections - m_ServerNetworkManager.NetworkConfig.ForceSamePrefabs = false; + authority.NetworkConfig.ForceSamePrefabs = false; // Create the valid network prefab m_NetworkPrefab = CreateNetworkObjectPrefab("ValidObject"); @@ -104,8 +97,9 @@ protected override void OnNewClientCreated(NetworkManager networkManager) public IEnumerator NetworkObjectDeserializationFailure() { m_CurrentLogLevel = LogLevel.Nothing; - var validSpawnedNetworkObjects = new List(); + var authoritySpawnedNetworkObjects = new List(); NetworkBehaviourWithNetworkVariables.ResetSpawnCount(); + var authority = GetAuthorityNetworkManager(); // Spawn NetworkObjects on the server side with half of them being the // invalid network prefabs to simulate NetworkObject synchronization failure @@ -113,47 +107,29 @@ public IEnumerator NetworkObjectDeserializationFailure() { if (i % 2 == 0) { - SpawnObject(m_InValidNetworkPrefab, m_ServerNetworkManager); + SpawnObject(m_InValidNetworkPrefab, authority); } else { // Keep track of the prefabs that should successfully spawn on the client side - validSpawnedNetworkObjects.Add(SpawnObject(m_NetworkPrefab, m_ServerNetworkManager)); + var instance = SpawnObject(m_NetworkPrefab, authority); + authoritySpawnedNetworkObjects.Add(instance.GetComponent()); } } // Assure the server-side spawned all NetworkObjects - yield return WaitForConditionOrTimeOut(() => NetworkBehaviourWithNetworkVariables.ServerSpawnCount == k_NumberToSpawn); + yield return WaitForConditionOrTimeOut(() => NetworkBehaviourWithNetworkVariables.AuthoritySpawnCount == k_NumberToSpawn); // Now spawn and connect a client that will fail to spawn half of the NetworkObjects spawned - yield return CreateAndStartNewClient(); + var newClient = CreateNewClient(); + yield return StartClient(newClient); if (m_UseHost) { - var delayCounter = 0; - while (m_ClientNetworkManagers.Length == 0) - { - delayCounter++; - Assert.True(delayCounter < 30, "TimeOut waiting for client to spawn!"); - yield return s_DefaultWaitForTick; - } - delayCounter = 0; - while (!m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId].ContainsKey(m_ClientNetworkManagers[0].LocalClientId)) - { - delayCounter++; - if (delayCounter >= 30) - { - VerboseDebug("Trap!"); - } - Assert.True(delayCounter < 30, "TimeOut waiting for client to spawn!"); - yield return s_DefaultWaitForTick; - } - - - var serverSideClientPlayerComponent = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); - var serverSideHostPlayerComponent = m_ServerNetworkManager.LocalClient.PlayerObject.GetComponent(); - var clientSidePlayerComponent = m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent(); - var clientSideHostPlayerComponent = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ServerNetworkManager.LocalClientId].GetComponent(); + var serverSideClientPlayerComponent = m_PlayerNetworkObjects[authority.LocalClientId][newClient.LocalClientId].GetComponent(); + var serverSideHostPlayerComponent = authority.LocalClient.PlayerObject.GetComponent(); + var clientSidePlayerComponent = newClient.LocalClient.PlayerObject.GetComponent(); + var clientSideHostPlayerComponent = m_PlayerNetworkObjects[newClient.LocalClientId][authority.LocalClientId].GetComponent(); var modeText = m_DistributedAuthority ? "owner" : "server"; // Validate that the client side player values match the server side value of the client's player Assert.IsTrue(serverSideClientPlayerComponent.NetworkVariableData1.Value == clientSidePlayerComponent.NetworkVariableData1.Value, @@ -192,16 +168,15 @@ public IEnumerator NetworkObjectDeserializationFailure() else { // Spawn and connect another client when running as a server - yield return CreateAndStartNewClient(); - yield return WaitForConditionOrTimeOut(() => m_PlayerNetworkObjects[2].Count > 1); - AssertOnTimeout($"Timed out waiting for second client to have access to the first client's cloned player object!"); + var secondClient = CreateNewClient(); + yield return StartClient(secondClient); - var clientSide1PlayerComponent = m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent(); - var clientSide2Player1Clone = m_PlayerNetworkObjects[2][clientSide1PlayerComponent.OwnerClientId].GetComponent(); + var clientSide1PlayerComponent = newClient.LocalClient.PlayerObject.GetComponent(); + var clientSide2Player1Clone = m_PlayerNetworkObjects[secondClient.LocalClientId][clientSide1PlayerComponent.OwnerClientId].GetComponent(); var clientOneId = clientSide1PlayerComponent.OwnerClientId; - var clientSide2PlayerComponent = m_ClientNetworkManagers[1].LocalClient.PlayerObject.GetComponent(); - var clientSide1Player2Clone = m_PlayerNetworkObjects[1][clientSide2PlayerComponent.OwnerClientId].GetComponent(); + var clientSide2PlayerComponent = secondClient.LocalClient.PlayerObject.GetComponent(); + var clientSide1Player2Clone = m_PlayerNetworkObjects[newClient.LocalClientId][clientSide2PlayerComponent.OwnerClientId].GetComponent(); var clientTwoId = clientSide2PlayerComponent.OwnerClientId; // Validate that client one's 2nd and 4th NetworkVariables for the local and clone instances match and the other two do not @@ -244,75 +219,57 @@ public IEnumerator NetworkObjectDeserializationFailure() } } - // DANGO-TODO: This scenario is only possible to do if we add a DA-Server to mock the CMB Service or we integrate the CMB Service AND we have updated NetworkVariable permissions - // to only allow the service to write. For now, we will skip this validation for distributed authority - if (!m_DistributedAuthority) + // Now validate all of the NetworkVariable values match to assure everything synchronized properly + foreach (var spawnedObject in authoritySpawnedNetworkObjects) { - // Now validate all of the NetworkVariable values match to assure everything synchronized properly - foreach (var spawnedObject in validSpawnedNetworkObjects) + foreach (var networkManager in m_NetworkManagers) { - foreach (var clientNetworkManager in m_ClientNetworkManagers) + if (networkManager == authority) { - //Validate that the connected client has spawned all of the instances that shouldn't have failed. - var clientSideNetworkObjects = s_GlobalNetworkObjects[clientNetworkManager.LocalClientId]; + continue; + } + //Validate that the connected client has spawned all of the instances that shouldn't have failed. + var clientSideNetworkObjects = s_GlobalNetworkObjects[networkManager.LocalClientId]; - Assert.IsTrue(NetworkBehaviourWithNetworkVariables.ClientSpawnCount[clientNetworkManager.LocalClientId] == validSpawnedNetworkObjects.Count, $"Client-{clientNetworkManager.LocalClientId} spawned " + - $"({NetworkBehaviourWithNetworkVariables.ClientSpawnCount}) {nameof(NetworkObject)}s but the expected number of {nameof(NetworkObject)}s should have been ({validSpawnedNetworkObjects.Count})!"); + Assert.IsTrue(NetworkBehaviourWithNetworkVariables.NonAuthoritySpawnCount[networkManager.LocalClientId] == authoritySpawnedNetworkObjects.Count, $"Client-{networkManager.LocalClientId} spawned " + + $"({NetworkBehaviourWithNetworkVariables.NonAuthoritySpawnCount}) {nameof(NetworkObject)}s but the expected number of {nameof(NetworkObject)}s should have been ({authoritySpawnedNetworkObjects.Count})!"); - var spawnedNetworkObject = spawnedObject.GetComponent(); - Assert.IsTrue(clientSideNetworkObjects.ContainsKey(spawnedNetworkObject.NetworkObjectId), $"Failed to find valid spawned {nameof(NetworkObject)} on the client-side with a " + - $"{nameof(NetworkObject.NetworkObjectId)} of {spawnedNetworkObject.NetworkObjectId}"); + Assert.IsTrue(clientSideNetworkObjects.ContainsKey(spawnedObject.NetworkObjectId), $"Failed to find valid spawned {nameof(NetworkObject)} on the client-side with a " + + $"{nameof(NetworkObject.NetworkObjectId)} of {spawnedObject.NetworkObjectId}"); - var clientSideObject = clientSideNetworkObjects[spawnedNetworkObject.NetworkObjectId]; - Assert.IsTrue(clientSideObject.NetworkManager == clientNetworkManager, $"Client-side object {clientSideObject}'s {nameof(NetworkManager)} is not valid!"); + var clientSideObject = clientSideNetworkObjects[spawnedObject.NetworkObjectId]; + Assert.IsTrue(clientSideObject.NetworkManager == networkManager, $"Client-side object {clientSideObject}'s {nameof(NetworkManager)} is not valid!"); - ValidateNetworkBehaviourWithNetworkVariables(spawnedNetworkObject, clientSideObject); - } + ValidateNetworkBehaviourWithNetworkVariables(spawnedObject, clientSideObject); } } } - private void ValidateNetworkBehaviourWithNetworkVariables(NetworkObject serverSideNetworkObject, NetworkObject clientSideNetworkObject) + private void ValidateNetworkBehaviourWithNetworkVariables(NetworkObject authorityNetworkObject, NetworkObject nonAuthorityNetworkObject) { - var serverSideComponent = serverSideNetworkObject.GetComponent(); - var clientSideComponent = clientSideNetworkObject.GetComponent(); + var authorityComponent = authorityNetworkObject.GetComponent(); + var nonAuthorityComponent = nonAuthorityNetworkObject.GetComponent(); string netVarName1 = nameof(NetworkBehaviourWithNetworkVariables.NetworkVariableData1); string netVarName2 = nameof(NetworkBehaviourWithNetworkVariables.NetworkVariableData1); string netVarName3 = nameof(NetworkBehaviourWithNetworkVariables.NetworkVariableData1); string netVarName4 = nameof(NetworkBehaviourWithNetworkVariables.NetworkVariableData1); - Assert.IsTrue(serverSideComponent.NetworkVariableData1.Count == clientSideComponent.NetworkVariableData1.Count, $"[{serverSideComponent.name}:{netVarName1}] Server side {nameof(NetworkList)} " + - $"count ({serverSideComponent.NetworkVariableData1.Count}) does not match the client side {nameof(NetworkList)} count ({clientSideComponent.NetworkVariableData1.Count})!"); + Assert.IsTrue(authorityComponent.NetworkVariableData1.Count == nonAuthorityComponent.NetworkVariableData1.Count, $"[{authorityComponent.name}:{netVarName1}] Server side {nameof(NetworkList)} " + + $"count ({authorityComponent.NetworkVariableData1.Count}) does not match the client side {nameof(NetworkList)} count ({nonAuthorityComponent.NetworkVariableData1.Count})!"); - for (int i = 0; i < serverSideComponent.NetworkVariableData1.Count; i++) + for (int i = 0; i < authorityComponent.NetworkVariableData1.Count; i++) { - Assert.IsTrue(serverSideComponent.NetworkVariableData1[i] == clientSideComponent.NetworkVariableData1[i], $"[{serverSideComponent.name}:{netVarName1}][Index:{i}] Server side instance value " + - $"({serverSideComponent.NetworkVariableData1[i]}) does not match the client side instance value ({clientSideComponent.NetworkVariableData1[i]})!"); + Assert.IsTrue(authorityComponent.NetworkVariableData1[i] == nonAuthorityComponent.NetworkVariableData1[i], $"[{authorityComponent.name}:{netVarName1}][Index:{i}] Server side instance value " + + $"({authorityComponent.NetworkVariableData1[i]}) does not match the client side instance value ({nonAuthorityComponent.NetworkVariableData1[i]})!"); } - Assert.IsTrue(serverSideComponent.NetworkVariableData2.Value == clientSideComponent.NetworkVariableData2.Value, $"[{serverSideComponent.name}:{netVarName2}] Server side instance value ({serverSideComponent.NetworkVariableData2.Value}) " + - $"does not match the client side instance value ({clientSideComponent.NetworkVariableData2.Value})!"); - Assert.IsTrue(serverSideComponent.NetworkVariableData3.Value == clientSideComponent.NetworkVariableData3.Value, $"[{serverSideComponent.name}:{netVarName3}] Server side instance value ({serverSideComponent.NetworkVariableData3.Value}) " + - $"does not match the client side instance value ({clientSideComponent.NetworkVariableData3.Value})!"); - Assert.IsTrue(serverSideComponent.NetworkVariableData4.Value == clientSideComponent.NetworkVariableData4.Value, $"[{serverSideComponent.name}:{netVarName4}] Server side instance value ({serverSideComponent.NetworkVariableData4.Value}) " + - $"does not match the client side instance value ({clientSideComponent.NetworkVariableData4.Value})!"); - } - - - private bool ClientSpawnedNetworkObjects(List spawnedObjectList) - { - var clientSideNetworkObjects = s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId]; - - foreach (var spawnedObject in spawnedObjectList) - { - var serverSideSpawnedNetworkObject = spawnedObject.GetComponent(); - if (!clientSideNetworkObjects.ContainsKey(serverSideSpawnedNetworkObject.NetworkObjectId)) - { - return false; - } - } - return true; + Assert.IsTrue(authorityComponent.NetworkVariableData2.Value == nonAuthorityComponent.NetworkVariableData2.Value, $"[{authorityComponent.name}:{netVarName2}] Server side instance value ({authorityComponent.NetworkVariableData2.Value}) " + + $"does not match the client side instance value ({nonAuthorityComponent.NetworkVariableData2.Value})!"); + Assert.IsTrue(authorityComponent.NetworkVariableData3.Value == nonAuthorityComponent.NetworkVariableData3.Value, $"[{authorityComponent.name}:{netVarName3}] Server side instance value ({authorityComponent.NetworkVariableData3.Value}) " + + $"does not match the client side instance value ({nonAuthorityComponent.NetworkVariableData3.Value})!"); + Assert.IsTrue(authorityComponent.NetworkVariableData4.Value == nonAuthorityComponent.NetworkVariableData4.Value, $"[{authorityComponent.name}:{netVarName4}] Server side instance value ({authorityComponent.NetworkVariableData4.Value}) " + + $"does not match the client side instance value ({nonAuthorityComponent.NetworkVariableData4.Value})!"); } /// @@ -322,7 +279,8 @@ private bool ClientSpawnedNetworkObjects(List spawnedObjectList) [UnityTest] public IEnumerator NetworkBehaviourSynchronization() { - m_ServerNetworkManager.LogLevel = LogLevel.Normal; + var authority = GetAuthorityNetworkManager(); + authority.LogLevel = LogLevel.Normal; m_CurrentLogLevel = LogLevel.Normal; NetworkBehaviourSynchronizeFailureComponent.ResetBehaviour(); @@ -331,28 +289,29 @@ public IEnumerator NetworkBehaviourSynchronization() // Spawn 11 more NetworkObjects where there should be 4 of each failure type for (int i = 0; i < numberOfObjectsToSpawn; i++) { - var synchronizationObject = SpawnObject(m_SynchronizationPrefab, m_ServerNetworkManager); + var synchronizationObject = SpawnObject(m_SynchronizationPrefab, authority); var synchronizationBehaviour = synchronizationObject.GetComponent(); synchronizationBehaviour.AssignNextFailureType(); spawnedObjectList.Add(synchronizationObject); } // Now spawn and connect a client that will fail to spawn half of the NetworkObjects spawned - yield return CreateAndStartNewClient(); + var newClient = CreateNewClient(); + yield return StartClient(newClient); // Validate that when a NetworkBehaviour fails to synchronize and is skipped over it does not // impact the rest of the NetworkBehaviours. - var clientSideNetworkObjects = s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId]; - yield return WaitForConditionOrTimeOut(() => ClientSpawnedNetworkObjects(spawnedObjectList)); + var clientSideNetworkObjects = s_GlobalNetworkObjects[newClient.LocalClientId]; + yield return WaitForSpawnedOnAllOrTimeOut(clientSideNetworkObjects.Values); AssertOnTimeout($"Timed out waiting for newly joined client to spawn all NetworkObjects!"); foreach (var spawnedObject in spawnedObjectList) { - var serverSideSpawnedNetworkObject = spawnedObject.GetComponent(); - var clientSideObject = clientSideNetworkObjects[serverSideSpawnedNetworkObject.NetworkObjectId]; - var clientSideSpawnedNetworkObject = clientSideObject.GetComponent(); + var authorityObject = spawnedObject.GetComponent(); + var nonAuthorityObject = clientSideNetworkObjects[authorityObject.NetworkObjectId]; + var clientSideSpawnedNetworkObject = nonAuthorityObject.GetComponent(); - ValidateNetworkBehaviourWithNetworkVariables(serverSideSpawnedNetworkObject, clientSideSpawnedNetworkObject); + ValidateNetworkBehaviourWithNetworkVariables(authorityObject, clientSideSpawnedNetworkObject); } } @@ -362,12 +321,14 @@ public IEnumerator NetworkBehaviourSynchronization() [UnityTest] public IEnumerator NetworkBehaviourOnSynchronize() { - var serverSideInstance = SpawnObject(m_OnSynchronizePrefab, m_ServerNetworkManager).GetComponent(); + var authority = GetAuthorityNetworkManager(); + var serverSideInstance = SpawnObject(m_OnSynchronizePrefab, authority).GetComponent(); // Now spawn and connect a client that will have custom serialized data applied during the client synchronization process. - yield return CreateAndStartNewClient(); + var newClient = CreateNewClient(); + yield return StartClient(newClient); - var clientSideNetworkObjects = s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId]; + var clientSideNetworkObjects = s_GlobalNetworkObjects[newClient.LocalClientId]; var clientSideInstance = clientSideNetworkObjects[serverSideInstance.NetworkObjectId].GetComponent(); // Validate the values match @@ -386,13 +347,13 @@ public IEnumerator NetworkBehaviourOnSynchronize() /// internal class NetworkBehaviourWithNetworkVariables : NetworkBehaviour { - public static int ServerSpawnCount { get; internal set; } - public static readonly Dictionary ClientSpawnCount = new Dictionary(); + public static int AuthoritySpawnCount { get; internal set; } + public static readonly Dictionary NonAuthoritySpawnCount = new Dictionary(); public static void ResetSpawnCount() { - ServerSpawnCount = 0; - ClientSpawnCount.Clear(); + AuthoritySpawnCount = 0; + NonAuthoritySpawnCount.Clear(); } private const uint k_MinDataBlocks = 1; @@ -424,15 +385,15 @@ public override void OnNetworkSpawn() { if (IsServer) { - ServerSpawnCount++; + AuthoritySpawnCount++; } else { - if (!ClientSpawnCount.ContainsKey(NetworkManager.LocalClientId)) + if (!NonAuthoritySpawnCount.ContainsKey(NetworkManager.LocalClientId)) { - ClientSpawnCount.Add(NetworkManager.LocalClientId, 0); + NonAuthoritySpawnCount.Add(NetworkManager.LocalClientId, 0); } - ClientSpawnCount[NetworkManager.LocalClientId]++; + NonAuthoritySpawnCount[NetworkManager.LocalClientId]++; } base.OnNetworkSpawn(); @@ -496,8 +457,8 @@ public override void OnNetworkSpawn() internal class NetworkBehaviourSynchronizeFailureComponent : NetworkBehaviour { public static int NumberOfFailureTypes { get; internal set; } - public static int ServerSpawnCount { get; internal set; } - public static int ClientSpawnCount { get; internal set; } + public static int AuthoritySpawnCount { get; internal set; } + public static int NonAuthoritySpawnCount { get; internal set; } private static FailureTypes s_FailureType = FailureTypes.None; @@ -513,8 +474,8 @@ public enum FailureTypes public static void ResetBehaviour() { - ServerSpawnCount = 0; - ClientSpawnCount = 0; + AuthoritySpawnCount = 0; + NonAuthoritySpawnCount = 0; s_FailureType = FailureTypes.None; NumberOfFailureTypes = System.Enum.GetValues(typeof(FailureTypes)).Length; } @@ -595,6 +556,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } case FailureTypes.DontReadAnything: { + Debug.Log("Don't read anything is being run"); // Don't read anything break; } @@ -641,14 +603,14 @@ private void Awake() public override void OnNetworkSpawn() { - if (IsServer) + if (HasAuthority) { - ServerSpawnCount++; + AuthoritySpawnCount++; m_MyCustomData.GenerateData((ushort)Random.Range(1, 512)); } else { - ClientSpawnCount++; + NonAuthoritySpawnCount++; } base.OnNetworkSpawn(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index 6c09212a18..1ef0a13eb4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -488,8 +488,7 @@ private bool Object1IsNotVisibileToClient() m_ErrorLog.AppendLine($"{m_NetSpawnedObject1.name} is still visible to Client-{m_ClientWithoutVisibility}!"); } } - else - if (client.SpawnManager.SpawnedObjects[m_NetSpawnedObject1.NetworkObjectId].IsNetworkVisibleTo(m_ClientWithoutVisibility)) + else if (client.SpawnManager.SpawnedObjects[m_NetSpawnedObject1.NetworkObjectId].IsNetworkVisibleTo(m_ClientWithoutVisibility)) { m_ErrorLog.AppendLine($"Local instance of {m_NetSpawnedObject1.name} on Client-{client.LocalClientId} thinks Client-{m_ClientWithoutVisibility} still has visibility!"); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/InterpolationStopAndStartMotionTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/InterpolationStopAndStartMotionTest.cs index d064d7b3b2..3690d6fb6e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/InterpolationStopAndStartMotionTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/InterpolationStopAndStartMotionTest.cs @@ -101,7 +101,7 @@ private bool WaitForInstancesToFinishInterpolation() return true; } - [UnityTest] + [UnityTest, Ignore("Disabled due to instability. Tracked by MTT-15432.")] public IEnumerator StopAndStartMotion() { m_IsSecondPass = false; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs index e5fb5a4ba9..350f312973 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs @@ -418,8 +418,7 @@ private bool ShouldSyncAxis(bool first, bool second, bool lastValue) // make the last one disabled. return false; } - else - if (!first && !second) + else if (!first && !second) { // If both are disabled, then make the // last one enabled. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs index 61024d46da..f1ead09032 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs @@ -6,6 +6,8 @@ using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools.Utils; +using Quaternion = UnityEngine.Quaternion; +using Vector3 = UnityEngine.Vector3; namespace Unity.Netcode.RuntimeTests { @@ -17,6 +19,12 @@ public void MoveRpc(Vector3 newPosition) transform.position = newPosition; } + [Rpc(SendTo.Server)] + public void LocalMoveRpc(Vector3 newPosition) + { + transform.localPosition = newPosition; + } + [Rpc(SendTo.Server)] public void ScaleRpc(Vector3 newScale) { @@ -29,6 +37,12 @@ public void RotateRpc(Quaternion newRotation) transform.rotation = newRotation; } + [Rpc(SendTo.Server)] + public void LocalRotateRpc(Quaternion newRotation) + { + transform.localRotation = newRotation; + } + public bool ShouldSmooth = false; public bool ShouldMove = false; @@ -59,12 +73,22 @@ internal class NetworkTransformAnticipationTests : NetcodeIntegrationTest protected override bool m_SetupIsACoroutine => false; protected override bool m_TearDownIsACoroutine => false; + private GameObject m_TestPrefab; + protected override void OnPlayerPrefabGameObjectCreated() { m_PlayerPrefab.AddComponent(); m_PlayerPrefab.AddComponent(); } + protected override void OnServerAndClientsCreated() + { + m_TestPrefab = CreateNetworkObjectPrefab("child object"); + var transform = m_TestPrefab.AddComponent(); + transform.InLocalSpace = true; + m_TestPrefab.AddComponent(); + } + protected override void OnTimeTravelServerAndClientsConnected() { var serverComponent = GetServerComponent(); @@ -80,6 +104,13 @@ protected override void OnTimeTravelServerAndClientsConnected() otherClientComponent.transform.position = Vector3.zero; otherClientComponent.transform.localScale = Vector3.one; otherClientComponent.transform.rotation = Quaternion.LookRotation(Vector3.forward); + + var childObject = SpawnObject(m_TestPrefab, m_ServerNetworkManager); + childObject.transform.parent = serverComponent.transform; + childObject.transform.localPosition = Vector3.zero; + childObject.transform.localScale = Vector3.one; + childObject.transform.localRotation = Quaternion.LookRotation(Vector3.forward); + WaitForSpawnedOnAllOrTimeOutWithTimeTravel(childObject.GetComponent()); } public AnticipatedNetworkTransform GetTestComponent() @@ -115,16 +146,36 @@ public AnticipatedNetworkTransform GetOtherClientComponent() return null; } + public AnticipatedNetworkTransform GetChildComponent(AnticipatedNetworkTransform parent) + { + foreach (var tr in parent.GetComponentsInChildren()) + { + if (tr == parent) + { + continue; + } + + return tr; + } + + return null; + } + [Test] public void WhenAnticipating_ValueChangesImmediately() { var testComponent = GetTestComponent(); + var childComponent = GetChildComponent(testComponent); var quaternionComparer = new QuaternionEqualityComparer(0.000001f); testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + childComponent.AnticipateMove(new Vector3(0, 1, 2)); + childComponent.AnticipateScale(new Vector3(1, 2, 3)); + childComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + Assert.AreEqual(new Vector3(0, 1, 2), testComponent.transform.position); Assert.AreEqual(new Vector3(1, 2, 3), testComponent.transform.localScale); Assert.That(testComponent.transform.rotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. @@ -132,40 +183,71 @@ public void WhenAnticipating_ValueChangesImmediately() Assert.AreEqual(new Vector3(0, 1, 2), testComponent.AnticipatedState.Position); Assert.AreEqual(new Vector3(1, 2, 3), testComponent.AnticipatedState.Scale); Assert.That(testComponent.AnticipatedState.Rotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. + + Assert.AreEqual(new Vector3(0, 1, 2), childComponent.transform.localPosition); + Assert.AreEqual(new Vector3(1, 2, 3), childComponent.transform.localScale); + Assert.That(childComponent.transform.localRotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. + + Assert.AreEqual(new Vector3(0, 1, 2), childComponent.AnticipatedState.Position); + Assert.AreEqual(new Vector3(1, 2, 3), childComponent.AnticipatedState.Scale); + Assert.That(childComponent.AnticipatedState.Rotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. } [Test] public void WhenAnticipating_AuthoritativeValueDoesNotChange() { var testComponent = GetTestComponent(); + var childComponent = GetChildComponent(testComponent); var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; var startRotation = testComponent.transform.rotation; + var childStartPosition = childComponent.transform.localPosition; + var childStartScale = childComponent.transform.localScale; + var childStartRotation = childComponent.transform.localRotation; + testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + childComponent.AnticipateMove(new Vector3(0, 1, 2)); + childComponent.AnticipateScale(new Vector3(1, 2, 3)); + childComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + Assert.AreEqual(startPosition, testComponent.AuthoritativeState.Position); Assert.AreEqual(startScale, testComponent.AuthoritativeState.Scale); Assert.AreEqual(startRotation, testComponent.AuthoritativeState.Rotation); + + Assert.AreEqual(childStartPosition, childComponent.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, childComponent.AuthoritativeState.Scale); + Assert.AreEqual(childStartRotation, childComponent.AuthoritativeState.Rotation); } [Test] public void WhenAnticipating_ServerDoesNotChange() { var testComponent = GetTestComponent(); + var childComponent = GetChildComponent(testComponent); var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; var startRotation = testComponent.transform.rotation; + var childStartPosition = childComponent.transform.localPosition; + var childStartScale = childComponent.transform.localScale; + var childStartRotation = childComponent.transform.localRotation; + testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + childComponent.AnticipateMove(new Vector3(0, 1, 2)); + childComponent.AnticipateScale(new Vector3(1, 2, 3)); + childComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + var serverComponent = GetServerComponent(); + var serverChild = GetChildComponent(serverComponent); Assert.AreEqual(startPosition, serverComponent.AuthoritativeState.Position); Assert.AreEqual(startScale, serverComponent.AuthoritativeState.Scale); @@ -174,6 +256,13 @@ public void WhenAnticipating_ServerDoesNotChange() Assert.AreEqual(startScale, serverComponent.AnticipatedState.Scale); Assert.AreEqual(startRotation, serverComponent.AnticipatedState.Rotation); + Assert.AreEqual(childStartPosition, serverChild.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, serverChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(childStartRotation, serverChild.AuthoritativeState.Rotation); + Assert.AreEqual(childStartPosition, serverChild.AnticipatedState.Position); + Assert.AreEqual(childStartScale, serverChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(childStartRotation, serverChild.AnticipatedState.Rotation); + TimeTravel(2, 120); Assert.AreEqual(startPosition, serverComponent.AuthoritativeState.Position); @@ -182,22 +271,35 @@ public void WhenAnticipating_ServerDoesNotChange() Assert.AreEqual(startPosition, serverComponent.AnticipatedState.Position); Assert.AreEqual(startScale, serverComponent.AnticipatedState.Scale); Assert.AreEqual(startRotation, serverComponent.AnticipatedState.Rotation); + + Assert.AreEqual(childStartPosition, serverChild.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, serverChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(childStartRotation, serverChild.AuthoritativeState.Rotation); + Assert.AreEqual(childStartPosition, serverChild.AnticipatedState.Position); + Assert.AreEqual(childStartScale, serverChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(childStartRotation, serverChild.AnticipatedState.Rotation); } [Test] public void WhenAnticipating_OtherClientDoesNotChange() { var testComponent = GetTestComponent(); + var childComponent = GetChildComponent(testComponent); var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; var startRotation = testComponent.transform.rotation; + var childStartPosition = childComponent.transform.localPosition; + var childStartScale = childComponent.transform.localScale; + var childStartRotation = childComponent.transform.localRotation; + testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); var otherClientComponent = GetOtherClientComponent(); + var otherClientChild = GetChildComponent(otherClientComponent); Assert.AreEqual(startPosition, otherClientComponent.AuthoritativeState.Position); Assert.AreEqual(startScale, otherClientComponent.AuthoritativeState.Scale); @@ -206,6 +308,13 @@ public void WhenAnticipating_OtherClientDoesNotChange() Assert.AreEqual(startScale, otherClientComponent.AnticipatedState.Scale); Assert.AreEqual(startRotation, otherClientComponent.AnticipatedState.Rotation); + Assert.AreEqual(childStartPosition, otherClientChild.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, otherClientChild.AuthoritativeState.Scale); + Assert.AreEqual(childStartRotation, otherClientChild.AuthoritativeState.Rotation); + Assert.AreEqual(childStartPosition, otherClientChild.AnticipatedState.Position); + Assert.AreEqual(childStartScale, otherClientChild.AnticipatedState.Scale); + Assert.AreEqual(childStartRotation, otherClientChild.AnticipatedState.Rotation); + TimeTravel(2, 120); Assert.AreEqual(startPosition, otherClientComponent.AuthoritativeState.Position); @@ -214,24 +323,45 @@ public void WhenAnticipating_OtherClientDoesNotChange() Assert.AreEqual(startPosition, otherClientComponent.AnticipatedState.Position); Assert.AreEqual(startScale, otherClientComponent.AnticipatedState.Scale); Assert.AreEqual(startRotation, otherClientComponent.AnticipatedState.Rotation); + + Assert.AreEqual(childStartPosition, otherClientChild.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, otherClientChild.AuthoritativeState.Scale); + Assert.AreEqual(childStartRotation, otherClientChild.AuthoritativeState.Rotation); + Assert.AreEqual(childStartPosition, otherClientChild.AnticipatedState.Position); + Assert.AreEqual(childStartScale, otherClientChild.AnticipatedState.Scale); + Assert.AreEqual(childStartRotation, otherClientChild.AnticipatedState.Rotation); } [Test] public void WhenServerChangesSnapValue_ValuesAreUpdated() { var testComponent = GetTestComponent(); + var testChild = GetChildComponent(testComponent); var serverComponent = GetServerComponent(); + var serverChild = GetChildComponent(serverComponent); serverComponent.Interpolate = false; + serverChild.Interpolate = false; testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + testChild.AnticipateMove(new Vector3(0, 1, 2)); + testChild.AnticipateScale(new Vector3(1, 2, 3)); + testChild.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + var rpcComponent = testComponent.GetComponent(); rpcComponent.MoveRpc(new Vector3(2, 3, 4)); + var childRpcComponent = testChild.GetComponent(); + childRpcComponent.LocalMoveRpc(new Vector3(2, 3, 4)); - WaitForMessageReceivedWithTimeTravel(new List { m_ServerNetworkManager }); + WaitForMessagesReceivedWithTimeTravel(new List + { + typeof(RpcMessage), + typeof(RpcMessage), + }, new List { m_ServerNetworkManager }); var otherClientComponent = GetOtherClientComponent(); + var otherClientChild = GetChildComponent(otherClientComponent); WaitForConditionOrTimeOutWithTimeTravel(() => testComponent.AuthoritativeState.Position == serverComponent.transform.position && otherClientComponent.AuthoritativeState.Position == serverComponent.transform.position); @@ -242,6 +372,16 @@ public void WhenServerChangesSnapValue_ValuesAreUpdated() Assert.AreEqual(serverComponent.transform.position, otherClientComponent.transform.position); Assert.AreEqual(serverComponent.transform.position, otherClientComponent.AnticipatedState.Position); Assert.AreEqual(serverComponent.transform.position, otherClientComponent.AuthoritativeState.Position); + + Assert.AreEqual(serverChild.transform.localPosition, testChild.transform.localPosition); + Assert.AreNotEqual(serverChild.transform.localPosition, testChild.transform.position); + Assert.AreEqual(serverChild.transform.localPosition, testChild.AnticipatedState.Position); + Assert.AreEqual(serverChild.transform.localPosition, testChild.AuthoritativeState.Position); + + Assert.AreEqual(serverChild.transform.localPosition, otherClientChild.transform.localPosition); + Assert.AreNotEqual(serverChild.transform.localPosition, otherClientChild.transform.position); + Assert.AreEqual(serverChild.transform.localPosition, otherClientChild.AnticipatedState.Position); + Assert.AreEqual(serverChild.transform.localPosition, otherClientChild.AuthoritativeState.Position); } public void AssertQuaternionsAreEquivalent(Quaternion a, Quaternion b) @@ -264,15 +404,23 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() { var testComponent = GetTestComponent(); var otherClientComponent = GetOtherClientComponent(); + var testChild = GetChildComponent(testComponent); + var otherChild = GetChildComponent(otherClientComponent); testComponent.StaleDataHandling = StaleDataHandling.Ignore; otherClientComponent.StaleDataHandling = StaleDataHandling.Ignore; + testChild.StaleDataHandling = StaleDataHandling.Ignore; + otherChild.StaleDataHandling = StaleDataHandling.Ignore; var serverComponent = GetServerComponent(); + var serverChild = GetChildComponent(serverComponent); serverComponent.Interpolate = false; + serverChild.Interpolate = false; testComponent.GetComponent().ShouldSmooth = true; otherClientComponent.GetComponent().ShouldSmooth = true; + testChild.GetComponent().ShouldSmooth = true; + otherChild.GetComponent().ShouldSmooth = true; var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; @@ -287,17 +435,27 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() testComponent.AnticipateMove(anticipePosition); testComponent.AnticipateScale(anticipeScale); testComponent.AnticipateRotate(anticipeRotation); + testChild.AnticipateMove(anticipePosition); + testChild.AnticipateScale(anticipeScale); + testChild.AnticipateRotate(anticipeRotation); var rpcComponent = testComponent.GetComponent(); rpcComponent.MoveRpc(serverSetPosition); rpcComponent.RotateRpc(serverSetRotation); rpcComponent.ScaleRpc(serverSetScale); + var childRpcComponent = testChild.GetComponent(); + childRpcComponent.LocalMoveRpc(serverSetPosition); + childRpcComponent.LocalRotateRpc(serverSetRotation); + childRpcComponent.ScaleRpc(serverSetScale); WaitForMessagesReceivedWithTimeTravel(new List { typeof(RpcMessage), typeof(RpcMessage), typeof(RpcMessage), + typeof(RpcMessage), + typeof(RpcMessage), + typeof(RpcMessage), }, new List { m_ServerNetworkManager }); WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); @@ -327,6 +485,30 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testChild.transform.localPosition); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testChild.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testChild.transform.localRotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, testChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherChild.transform.localPosition); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherChild.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherChild.transform.localRotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.AuthoritativeState.Rotation); + for (var i = 1; i < 60; ++i) { TimeTravel(1f / 60f, 1); @@ -355,6 +537,30 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position); AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testChild.transform.localPosition); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testChild.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testChild.transform.localRotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, testChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherChild.transform.localPosition); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherChild.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherChild.transform.localRotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.AuthoritativeState.Rotation); } TimeTravel(1f / 60f, 1); @@ -381,6 +587,30 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position); AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.transform.localPosition); + AssertVectorsAreEquivalent(serverSetScale, testChild.transform.localScale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.transform.localRotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(serverSetScale, testChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, testChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.transform.localPosition); + AssertVectorsAreEquivalent(serverSetScale, otherChild.transform.localScale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.transform.localRotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.AuthoritativeState.Rotation); } [Test] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs deleted file mode 100644 index 089eeee542..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs +++ /dev/null @@ -1,80 +0,0 @@ -#if COM_UNITY_MODULES_PHYSICS2D -using System.Collections; -using NUnit.Framework; -using Unity.Netcode.Components; -using UnityEngine; -using UnityEngine.TestTools; -using Unity.Netcode.TestHelpers.Runtime; - -namespace Unity.Netcode.RuntimeTests -{ - internal class NetworkRigidbody2DDynamicTest : NetworkRigidbody2DTestBase - { - public override bool Kinematic => false; - } - - internal class NetworkRigidbody2DKinematicTest : NetworkRigidbody2DTestBase - { - public override bool Kinematic => true; - } - - public abstract class NetworkRigidbody2DTestBase : NetcodeIntegrationTest - { - protected override int NumberOfClients => 1; - - public abstract bool Kinematic { get; } - - protected override void OnCreatePlayerPrefab() - { - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.GetComponent().interpolation = RigidbodyInterpolation2D.Interpolate; - m_PlayerPrefab.GetComponent().isKinematic = Kinematic; - } - - /// - /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. - /// - /// - [UnityTest] - public IEnumerator TestRigidbodyKinematicEnableDisable() - { - // This is the *SERVER VERSION* of the *CLIENT PLAYER* - var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); - yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult); - var serverPlayer = serverClientPlayerResult.Result.gameObject; - - // This is the *CLIENT VERSION* of the *CLIENT PLAYER* - var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); - yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult); - var clientPlayer = clientClientPlayerResult.Result.gameObject; - - Assert.IsNotNull(serverPlayer); - Assert.IsNotNull(clientPlayer); - - yield return WaitForTicks(m_ServerNetworkManager, 5); - - // server rigidbody has authority and should have a kinematic mode of false - Assert.True(serverPlayer.GetComponent().isKinematic == Kinematic); - Assert.AreEqual(RigidbodyInterpolation2D.Interpolate, serverPlayer.GetComponent().interpolation); - - // client rigidbody has no authority and should have a kinematic mode of true - Assert.True(clientPlayer.GetComponent().isKinematic); - Assert.AreEqual(RigidbodyInterpolation2D.None, clientPlayer.GetComponent().interpolation); - - // despawn the server player, (but keep it around on the server) - serverPlayer.GetComponent().Despawn(false); - - yield return WaitForTicks(m_ServerNetworkManager, 5); - - // This should equal Kinematic - Assert.IsTrue(serverPlayer.GetComponent().isKinematic == Kinematic); - - yield return WaitForTicks(m_ServerNetworkManager, 5); - - Assert.IsTrue(clientPlayer == null); // safety check that object is actually despawned. - } - } -} -#endif // COM_UNITY_MODULES_PHYSICS2D diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs.meta deleted file mode 100644 index 48faa513f7..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: f33b2f298cbb7b248b2a76ba48ee1d53 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs index 4509797369..bf5b77b42f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs @@ -1,7 +1,6 @@ -#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D +#if COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D using System.Collections; using System.Collections.Generic; -using System.Text; using NUnit.Framework; using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; @@ -10,491 +9,231 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixture(RigidbodyInterpolation.Interpolate, true, true)] // This should be allowed under all condistions when using Rigidbody motion - [TestFixture(RigidbodyInterpolation.Extrapolate, true, true)] // This should not allow extrapolation on non-auth instances when using Rigidbody motion & NT interpolation - [TestFixture(RigidbodyInterpolation.Extrapolate, false, true)] // This should allow extrapolation on non-auth instances when using Rigidbody & NT has no interpolation - [TestFixture(RigidbodyInterpolation.Interpolate, true, false)] // This should not allow kinematic instances to have Rigidbody interpolation enabled - [TestFixture(RigidbodyInterpolation.Interpolate, false, false)] // Testing that rigid body interpolation remains the same if NT interpolate is disabled + [TestFixture(HostOrServer.Server)] + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.DAHost)] internal class NetworkRigidbodyTest : NetcodeIntegrationTest { protected override int NumberOfClients => 1; - private bool m_NetworkTransformInterpolate; - private bool m_UseRigidBodyForMotion; - private RigidbodyInterpolation m_RigidbodyInterpolation; - public NetworkRigidbodyTest(RigidbodyInterpolation rigidbodyInterpolation, bool networkTransformInterpolate, bool useRigidbodyForMotion) - { - m_RigidbodyInterpolation = rigidbodyInterpolation; - m_NetworkTransformInterpolate = networkTransformInterpolate; - m_UseRigidBodyForMotion = useRigidbodyForMotion; - } - - protected override void OnCreatePlayerPrefab() - { - var networkTransform = m_PlayerPrefab.AddComponent(); - networkTransform.Interpolate = m_NetworkTransformInterpolate; - var rigidbody = m_PlayerPrefab.AddComponent(); - rigidbody.interpolation = m_RigidbodyInterpolation; - var networkRigidbody = m_PlayerPrefab.AddComponent(); - networkRigidbody.UseRigidBodyForMotion = m_UseRigidBodyForMotion; - } - - /// - /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. - /// - /// - [UnityTest] - public IEnumerator TestRigidbodyKinematicEnableDisable() - { - // This is the *SERVER VERSION* of the *CLIENT PLAYER* - var serverClientPlayerInstance = m_ServerNetworkManager.ConnectedClients[m_ClientNetworkManagers[0].LocalClientId].PlayerObject; - - // This is the *CLIENT VERSION* of the *CLIENT PLAYER* - var clientPlayerInstance = m_ClientNetworkManagers[0].LocalClient.PlayerObject; - - Assert.IsNotNull(serverClientPlayerInstance, $"{nameof(serverClientPlayerInstance)} is null!"); - Assert.IsNotNull(clientPlayerInstance, $"{nameof(clientPlayerInstance)} is null!"); - - var serverClientInstanceRigidBody = serverClientPlayerInstance.GetComponent(); - var clientRigidBody = clientPlayerInstance.GetComponent(); - - if (m_UseRigidBodyForMotion) - { - var interpolateCompareNonAuthoritative = m_NetworkTransformInterpolate ? RigidbodyInterpolation.Interpolate : m_RigidbodyInterpolation; - - // Server authoritative NT should yield non-kinematic mode for the server-side player instance - Assert.False(serverClientInstanceRigidBody.isKinematic, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is kinematic!"); - - // The authoritative instance can be None, Interpolate, or Extrapolate for the Rigidbody interpolation settings. - Assert.AreEqual(m_RigidbodyInterpolation, serverClientInstanceRigidBody.interpolation, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {serverClientInstanceRigidBody.interpolation} and not {m_RigidbodyInterpolation}!"); - - // Server authoritative NT should yield kinematic mode for the client-side player instance - Assert.True(clientRigidBody.isKinematic, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic!"); - - // When using Rigidbody motion, authoritative and non-authoritative Rigidbody interpolation settings should be preserved (except when extrapolation is used - Assert.AreEqual(interpolateCompareNonAuthoritative, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {interpolateCompareNonAuthoritative}!"); - } - else + private List<(RigidbodyInterpolation interpolationType, bool enableInterpolation, bool useRigidbodyForMotion)> m_TestConfigurations = + new List<(RigidbodyInterpolation interpolationType, bool enableInterpolation, bool useRigidbodyForMotion)>() { - // server rigidbody has authority and should not be kinematic - Assert.False(serverClientInstanceRigidBody.isKinematic, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is kinematic!"); - Assert.AreEqual(RigidbodyInterpolation.Interpolate, serverClientInstanceRigidBody.interpolation, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {serverClientInstanceRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); - - // Server authoritative NT should yield kinematic mode for the client-side player instance - Assert.True(clientRigidBody.isKinematic, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic!"); - - // client rigidbody has no authority with NT interpolation disabled should allow Rigidbody interpolation - if (!m_NetworkTransformInterpolate) - { - Assert.AreEqual(RigidbodyInterpolation.Interpolate, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); - } - else - { - Assert.AreEqual(RigidbodyInterpolation.None, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.None)}!"); - } - } - - // despawn the server player (but keep it around on the server) - serverClientPlayerInstance.Despawn(false); - - yield return WaitForConditionOrTimeOut(() => !serverClientPlayerInstance.IsSpawned && !clientPlayerInstance.IsSpawned); - AssertOnTimeout("Timed out waiting for client player to despawn on both server and client!"); - - // When despawned, we should always be kinematic (i.e. don't apply physics when despawned) - Assert.True(serverClientInstanceRigidBody.isKinematic, $"[Server-Side][Despawned] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic when despawned!"); - Assert.IsTrue(clientPlayerInstance == null, $"[Client-Side] Player {nameof(NetworkObject)} is not null!"); - } - } - - internal class ContactEventTransformHelperWithInfo : ContactEventTransformHelper, IContactEventHandlerWithInfo - { - public ContactEventHandlerInfo GetContactEventHandlerInfo() - { - var contactEventHandlerInfo = new ContactEventHandlerInfo() - { - HasContactEventPriority = IsOwner, - ProvideNonRigidBodyContactEvents = m_EnableNonRigidbodyContacts.Value, + (RigidbodyInterpolation.Interpolate, true, true), // This should be allowed under all condistions when using Rigidbody motion + (RigidbodyInterpolation.Extrapolate, true, true), // This should not allow extrapolation on non-auth instances when using Rigidbody motion & NT interpolation + (RigidbodyInterpolation.Extrapolate, false, true), // This should allow extrapolation on non-auth instances when using Rigidbody & NT has no interpolation + (RigidbodyInterpolation.Interpolate, true, false), // This should not allow kinematic instances to have Rigidbody interpolation enabled + (RigidbodyInterpolation.Interpolate, false, false) // Testing that rigidbody interpolation remains the same if NT interpolate is disabled }; - return contactEventHandlerInfo; - } - - protected override void OnRegisterForContactEvents(bool isRegistering) - { - RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering); - } - } + /// + /// The current test configuration applied to the current test running. + /// + private (RigidbodyInterpolation interpolationType, bool enableInterpolation, bool useRigidbodyForMotion) m_CurrentConfiguration; - internal class ContactEventTransformHelper : NetworkTransform, IContactEventHandler - { - public static Vector3 SessionOwnerSpawnPoint; - public static Vector3 ClientSpawnPoint; - public static bool VerboseDebug; - public enum HelperStates + public NetworkRigidbodyTest(HostOrServer hostOrServer) : base(hostOrServer) { - None, - MoveForward, } - private HelperStates m_HelperState; - - public void SetHelperState(HelperStates state) - { - m_HelperState = state; - if (!m_NetworkRigidbody.IsKinematic()) - { - m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero; - m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero; - } - m_NetworkRigidbody.Rigidbody.isKinematic = m_HelperState == HelperStates.None; - if (!m_NetworkRigidbody.IsKinematic()) - { - m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero; - m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero; - } + /// + /// Base prefab for and + /// + private GameObject m_RigidbodyPrefab; + private NetworkTransform m_3DNetworkTransform; + private Rigidbody m_PrefabRigidbody; + private NetworkRigidbody m_PrefabNetworkRigidbody; + private NetworkObject m_3DAuthorityInstance; - } + /// + /// Base prefab for and + /// + private GameObject m_Rigidbody2DPrefab; + private NetworkTransform m_2DNetworkTransform; + private Rigidbody2D m_PrefabRigidbody2D; + private NetworkRigidbody2D m_PrefabNetworkRigidbody2D; + private NetworkObject m_2DAuthorityInstance; - protected struct ContactEventInfo + protected override void OnServerAndClientsCreated() { - public ulong EventId; - public Vector3 AveragedCollisionNormal; - public Rigidbody CollidingBody; - public Vector3 ContactPoint; - } + m_RigidbodyPrefab = CreateNetworkObjectPrefab("RBTest"); + m_3DNetworkTransform = m_RigidbodyPrefab.AddComponent(); + m_PrefabRigidbody = m_RigidbodyPrefab.AddComponent(); + m_PrefabNetworkRigidbody = m_RigidbodyPrefab.AddComponent(); - protected List m_ContactEvents = new List(); + m_Rigidbody2DPrefab = CreateNetworkObjectPrefab("RB2DTest"); + m_2DNetworkTransform = m_Rigidbody2DPrefab.AddComponent(); + m_PrefabRigidbody2D = m_Rigidbody2DPrefab.AddComponent(); + m_PrefabNetworkRigidbody2D = m_Rigidbody2DPrefab.AddComponent(); - protected NetworkVariable m_EnableNonRigidbodyContacts = new NetworkVariable(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); - - protected NetworkRigidbody m_NetworkRigidbody; - public ContactEventTransformHelper Target; - - public bool HasContactEvents() - { - return m_ContactEvents.Count > 0; + base.OnServerAndClientsCreated(); } - public Rigidbody GetRigidbody() + private string m_ConfigHeader; + private void ApplyCurrentTestConfiguration() { - return m_NetworkRigidbody.Rigidbody; - } - - public bool HadContactWith(ContactEventTransformHelper otherObject) - { - if (otherObject == null) - { - return false; - } - foreach (var contactEvent in m_ContactEvents) - { - if (contactEvent.CollidingBody == otherObject.m_NetworkRigidbody.Rigidbody) - { - return true; - } - } - return false; - } + // Configure both 3D and 2D versions based on the current test configuration + m_3DNetworkTransform.Interpolate = m_CurrentConfiguration.enableInterpolation; + m_PrefabRigidbody.interpolation = m_CurrentConfiguration.interpolationType; + m_PrefabNetworkRigidbody.UseRigidBodyForMotion = m_CurrentConfiguration.useRigidbodyForMotion; + m_2DNetworkTransform.Interpolate = m_CurrentConfiguration.enableInterpolation; + m_PrefabRigidbody2D.interpolation = m_CurrentConfiguration.interpolationType == RigidbodyInterpolation.Interpolate ? RigidbodyInterpolation2D.Interpolate : RigidbodyInterpolation2D.Extrapolate; + m_PrefabNetworkRigidbody2D.UseRigidBodyForMotion = m_CurrentConfiguration.useRigidbodyForMotion; - protected virtual void CheckToStopMoving() - { - SetHelperState(HadContactWith(Target) ? HelperStates.None : HelperStates.MoveForward); + // Build a header used in assert messages + m_ConfigHeader = $"[{m_CurrentConfiguration.interpolationType}][Interpolate: {m_CurrentConfiguration.enableInterpolation}][RB-Motion: {m_CurrentConfiguration.useRigidbodyForMotion}]"; } - public void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default) + /// + /// Iterates through the to validate various + /// Rigidbody interpolation settings and kinematic states for authority and non-authority + /// instances. + /// + [UnityTest] + public IEnumerator TestRigidbodyKinematicEnableDisable() { - if (Target == null) + foreach (var configuration in m_TestConfigurations) { - return; - } + m_CurrentConfiguration = configuration; + ApplyCurrentTestConfiguration(); - if (collidingBody != null) - { - Log($">>>>>>> contact event with {collidingBody.name}!"); - } - else - { - Log($">>>>>>> contact event with non-rigidbody!"); - } + // Host, Server, DAHost/Session-owner are spawn authority + yield return RunTestConfiguration(); - m_ContactEvents.Add(new ContactEventInfo() - { - EventId = eventId, - AveragedCollisionNormal = averagedCollisionNormal, - CollidingBody = collidingBody, - ContactPoint = contactPoint, - }); - CheckToStopMoving(); - } - - private void SetInitialPositionClientServer() - { - if (IsServer) - { - if (!NetworkManager.DistributedAuthorityMode && !IsLocalPlayer) - { - transform.position = ClientSpawnPoint; - m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; - } - else + // When using distributed authority, swap the session owner with + // the non-session owner client as being the spawn authority. + if (m_DistributedAuthority) { - transform.position = SessionOwnerSpawnPoint; - m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint; + yield return RunTestConfiguration(true); } } - else - { - transform.position = ClientSpawnPoint; - m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; - } } - private void SetInitialPositionDistributedAuthority() - { - if (HasAuthority) + /// + /// Validates the current applied test configuration. + /// + private IEnumerator RunTestConfiguration(bool swapAuthority = false) + { + // The authority is the "spawn authority". + // Distributed authority runs this a second time with a non-session owner client being the + // spawn authority to validate that scenario works correctly. + var authority = !swapAuthority ? GetAuthorityNetworkManager() : GetNonAuthorityNetworkManager(); + var nonAuthority = !swapAuthority ? GetNonAuthorityNetworkManager() : GetAuthorityNetworkManager(); + + // Spawn instances of both the 3D and 2D prefabs configured for the current test. + m_3DAuthorityInstance = SpawnObject(m_RigidbodyPrefab, authority).GetComponent(); + yield return WaitForSpawnedOnAllOrTimeOut(m_3DAuthorityInstance); + AssertOnTimeout($"Failed to spawn {m_3DAuthorityInstance.name} on all clients!"); + + m_2DAuthorityInstance = SpawnObject(m_Rigidbody2DPrefab, authority).GetComponent(); + yield return WaitForSpawnedOnAllOrTimeOut(m_2DAuthorityInstance); + AssertOnTimeout($"Failed to spawn {m_2DAuthorityInstance.name} on all clients!"); + + // Test 3D Rigidbody + #region 3D Rigidbody validation + var authorityRigidbody = m_3DAuthorityInstance.GetComponent(); + var nonAuthorityInstance = nonAuthority.SpawnManager.SpawnedObjects[m_3DAuthorityInstance.NetworkObjectId]; + var nonAuthorityRigidbody = nonAuthorityInstance.GetComponent(); + var authorityHeader = $"{m_ConfigHeader}[Authority] Client-{authority.LocalClientId}'s instance of {m_3DAuthorityInstance.name}"; + // The authority instance should always be non-kinematic + Assert.False(authorityRigidbody.isKinematic, $"{authorityHeader} is kinematic!"); + + var nonAuthorityHeader = $"{m_ConfigHeader}[Non-Authority] Client-{nonAuthority.LocalClientId}'s instance of {nonAuthorityInstance.name}"; + // Non-authority instances should always be kinematic + Assert.True(nonAuthorityRigidbody.isKinematic, $"{nonAuthorityHeader} is not kinematic!"); + var interpolateCompareNonAuthoritative = RigidbodyInterpolation.None; + + if (m_CurrentConfiguration.useRigidbodyForMotion) { - if (IsSessionOwner) - { - transform.position = SessionOwnerSpawnPoint; - m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint; - } - else - { - transform.position = ClientSpawnPoint; - m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; - } - } - } - - public override void OnNetworkSpawn() - { - m_NetworkRigidbody = GetComponent(); + // The authoritative instance can be None, Interpolate, or Extrapolate for the Rigidbody interpolation settings. + Assert.AreEqual(m_CurrentConfiguration.interpolationType, authorityRigidbody.interpolation, $"{authorityHeader} interpolation is {authorityRigidbody.interpolation} " + + $"and not {m_CurrentConfiguration.interpolationType}!"); - m_NetworkRigidbody.Rigidbody.maxLinearVelocity = 15; - m_NetworkRigidbody.Rigidbody.maxAngularVelocity = 10; + // When using Rigidbody motion, authoritative and non-authoritative Rigidbody interpolation settings should be preserved (except when extrapolation is used + interpolateCompareNonAuthoritative = m_CurrentConfiguration.enableInterpolation ? RigidbodyInterpolation.Interpolate : m_CurrentConfiguration.interpolationType; - if (NetworkManager.DistributedAuthorityMode) - { - SetInitialPositionDistributedAuthority(); } else { - SetInitialPositionClientServer(); - } - if (IsLocalPlayer) - { - RegisterForContactEvents(true); - } - else - { - m_NetworkRigidbody.Rigidbody.detectCollisions = false; - } - base.OnNetworkSpawn(); - } - - protected virtual void OnRegisterForContactEvents(bool isRegistering) - { - RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering); - } - - public void RegisterForContactEvents(bool isRegistering) - { - OnRegisterForContactEvents(isRegistering); - } - - private void FixedUpdate() - { - if (!IsSpawned || !IsOwner || m_HelperState != HelperStates.MoveForward) - { - return; - } - var distance = Vector3.Distance(Target.transform.position, transform.position); - var moveAmount = Mathf.Max(1.2f, distance); - // Head towards our target - var dir = (Target.transform.position - transform.position).normalized; - var deltaMove = dir * moveAmount * Time.fixedDeltaTime; - m_NetworkRigidbody.Rigidbody.MovePosition(m_NetworkRigidbody.Rigidbody.position + deltaMove); - + Assert.AreEqual(RigidbodyInterpolation.Interpolate, authorityRigidbody.interpolation, $"{authorityHeader} interpolation is {authorityRigidbody.interpolation} " + + $"and not {RigidbodyInterpolation.Interpolate}!"); - Log($" Loc: {transform.position} | Dest: {Target.transform.position} | Dist: {distance} | MoveDelta: {deltaMove}"); - } - - protected void Log(string msg) - { - if (VerboseDebug) - { - Debug.Log($"Client-{OwnerClientId} {msg}"); + // client rigidbody has no authority with NT interpolation disabled should allow Rigidbody interpolation + interpolateCompareNonAuthoritative = m_CurrentConfiguration.enableInterpolation ? RigidbodyInterpolation.None : RigidbodyInterpolation.Interpolate; } - } - } - - [TestFixture(HostOrServer.Host, ContactEventTypes.Default)] - [TestFixture(HostOrServer.DAHost, ContactEventTypes.Default)] - [TestFixture(HostOrServer.Host, ContactEventTypes.WithInfo)] - [TestFixture(HostOrServer.DAHost, ContactEventTypes.WithInfo)] - internal class RigidbodyContactEventManagerTests : IntegrationTestWithApproximation - { - protected override int NumberOfClients => 1; - - private GameObject m_RigidbodyContactEventManager; - public enum ContactEventTypes - { - Default, - WithInfo - } + Assert.AreEqual(interpolateCompareNonAuthoritative, nonAuthorityRigidbody.interpolation, $"{nonAuthorityHeader} interpolation is {nonAuthorityRigidbody.interpolation} " + + $"and not {interpolateCompareNonAuthoritative}!"); + #endregion - private ContactEventTypes m_ContactEventType; - private StringBuilder m_ErrorLogger = new StringBuilder(); + // Test 2D Rigidbody + #region 2D Rigidbody validation + var authorityRigidbody2D = m_2DAuthorityInstance.GetComponent(); + var nonAuthorityInstance2D = nonAuthority.SpawnManager.SpawnedObjects[m_2DAuthorityInstance.NetworkObjectId]; + var nonAuthorityRigidbody2D = nonAuthorityInstance2D.GetComponent(); - public RigidbodyContactEventManagerTests(HostOrServer hostOrServer, ContactEventTypes contactEventType) : base(hostOrServer) - { - m_ContactEventType = contactEventType; - } + authorityHeader = $"{m_ConfigHeader}[Authority] Client-{authority.LocalClientId}'s instance of {m_2DAuthorityInstance.name}"; + // The authority instance should always be non-kinematic + Assert.False(authorityRigidbody2D.bodyType == RigidbodyType2D.Kinematic, $"{authorityHeader} is kinematic!"); - protected override void OnCreatePlayerPrefab() - { - ContactEventTransformHelper.SessionOwnerSpawnPoint = GetRandomVector3(-4, -3); - ContactEventTransformHelper.ClientSpawnPoint = GetRandomVector3(3, 4); - if (m_ContactEventType == ContactEventTypes.Default) + nonAuthorityHeader = $"{m_ConfigHeader}[Non-Authority] Client-{nonAuthority.LocalClientId}'s instance of {nonAuthorityInstance.name}"; + // Non-authority instances should always be kinematic + Assert.True(nonAuthorityRigidbody2D.bodyType == RigidbodyType2D.Kinematic, $"{nonAuthorityHeader} is not kinematic!"); + var interpolateCompareNonAuthoritative2D = RigidbodyInterpolation2D.None; + var configInterpolation2D = m_CurrentConfiguration.interpolationType == RigidbodyInterpolation.Interpolate ? RigidbodyInterpolation2D.Interpolate : RigidbodyInterpolation2D.Extrapolate; + if (m_CurrentConfiguration.useRigidbodyForMotion) { - var helper = m_PlayerPrefab.AddComponent(); - helper.AuthorityMode = NetworkTransform.AuthorityModes.Owner; + // The authoritative instance can be None, Interpolate, or Extrapolate for the Rigidbody interpolation settings. + Assert.AreEqual(configInterpolation2D, authorityRigidbody2D.interpolation, $"{authorityHeader} interpolation is {authorityRigidbody2D.interpolation} " + + $"and not {m_CurrentConfiguration.interpolationType}!"); + + // When using Rigidbody motion, authoritative and non-authoritative Rigidbody interpolation settings should be preserved (except when extrapolation is used + interpolateCompareNonAuthoritative2D = m_CurrentConfiguration.enableInterpolation ? RigidbodyInterpolation2D.Interpolate : configInterpolation2D; } else { - var helperWithInfo = m_PlayerPrefab.AddComponent(); - helperWithInfo.AuthorityMode = NetworkTransform.AuthorityModes.Owner; - } - - var rigidbody = m_PlayerPrefab.AddComponent(); - rigidbody.useGravity = false; - rigidbody.isKinematic = true; - rigidbody.mass = 5.0f; - rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous; - var sphereCollider = m_PlayerPrefab.AddComponent(); - sphereCollider.radius = 0.5f; - sphereCollider.providesContacts = true; - - var networkRigidbody = m_PlayerPrefab.AddComponent(); - networkRigidbody.UseRigidBodyForMotion = true; - networkRigidbody.AutoUpdateKinematicState = false; - - m_RigidbodyContactEventManager = new GameObject(); - m_RigidbodyContactEventManager.AddComponent(); - } - + Assert.AreEqual(RigidbodyInterpolation2D.Interpolate, authorityRigidbody2D.interpolation, $"{authorityHeader} interpolation is {authorityRigidbody2D.interpolation} " + + $"and not {RigidbodyInterpolation2D.Interpolate}!"); - - private bool PlayersSpawnedInRightLocation() - { - var authority = GetAuthorityNetworkManager(); - var nonAuthority = GetNonAuthorityNetworkManager(); - - var position = authority.LocalClient.PlayerObject.transform.position; - if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, position)) - { - m_ErrorLogger.AppendLine($"Client-{authority.LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!"); - return false; - } - - position = nonAuthority.LocalClient.PlayerObject.transform.position; - if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position)) - { - m_ErrorLogger.AppendLine($"Client-{nonAuthority.LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!"); - return false; - } - var playerObject = (NetworkObject)null; - if (!authority.SpawnManager.SpawnedObjects.ContainsKey(nonAuthority.LocalClient.PlayerObject.NetworkObjectId)) - { - m_ErrorLogger.AppendLine($"Client-{authority.LocalClientId} cannot find a local spawned instance of Client-{nonAuthority.LocalClientId}'s player object!"); - return false; + // client rigidbody has no authority with NT interpolation disabled should allow Rigidbody interpolation + interpolateCompareNonAuthoritative2D = m_CurrentConfiguration.enableInterpolation ? RigidbodyInterpolation2D.None : RigidbodyInterpolation2D.Interpolate; } - playerObject = authority.SpawnManager.SpawnedObjects[nonAuthority.LocalClient.PlayerObject.NetworkObjectId]; - position = playerObject.transform.position; - if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position)) - { - m_ErrorLogger.AppendLine($"Client-{authority.LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!"); - return false; - } + Assert.AreEqual(interpolateCompareNonAuthoritative2D, nonAuthorityRigidbody2D.interpolation, $"{nonAuthorityHeader} interpolation is {nonAuthorityRigidbody2D.interpolation} " + + $"and not {interpolateCompareNonAuthoritative}!"); + #endregion - if (!nonAuthority.SpawnManager.SpawnedObjects.ContainsKey(authority.LocalClient.PlayerObject.NetworkObjectId)) - { - m_ErrorLogger.AppendLine($"Client-{nonAuthority.LocalClientId} cannot find a local spawned instance of Client-{authority.LocalClientId}'s player object!"); - return false; - } - playerObject = nonAuthority.SpawnManager.SpawnedObjects[authority.LocalClient.PlayerObject.NetworkObjectId]; - position = playerObject.transform.position; - if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, playerObject.transform.position)) - { - m_ErrorLogger.AppendLine($"Client-{nonAuthority.LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!"); - return false; - } - return true; + var spawnedInstances = new List() { m_3DAuthorityInstance, m_2DAuthorityInstance }; + m_3DAuthorityInstance.Despawn(); + m_2DAuthorityInstance.Despawn(); + yield return WaitForDespawnedOnAllOrTimeOut(spawnedInstances); + AssertOnTimeout($"Failed to de-spawn instances on all clients!"); + m_3DAuthorityInstance = null; + m_2DAuthorityInstance = null; } - - [UnityTest] - public IEnumerator TestContactEvents() + /// + /// Handle clean up in case of a failed test + /// + protected override IEnumerator OnTearDown() { - ContactEventTransformHelper.VerboseDebug = m_EnableVerboseDebug; - - m_PlayerPrefab.SetActive(false); - m_ErrorLogger.Clear(); - // Validate all instances are spawned in the right location - yield return WaitForConditionOrTimeOut(PlayersSpawnedInRightLocation); - AssertOnTimeout($"Timed out waiting for all player instances to spawn in the corect location:\n {m_ErrorLogger}"); - m_ErrorLogger.Clear(); - - var authority = GetAuthorityNetworkManager(); - var nonAuthority = GetNonAuthorityNetworkManager(); + // If either of these are not null then we most likely failed and didn't cleanup. - var sessionOwnerPlayer = m_ContactEventType == ContactEventTypes.Default ? authority.LocalClient.PlayerObject.GetComponent() : - authority.LocalClient.PlayerObject.GetComponent(); - var clientPlayer = m_ContactEventType == ContactEventTypes.Default ? nonAuthority.LocalClient.PlayerObject.GetComponent() : - nonAuthority.LocalClient.PlayerObject.GetComponent(); - - // Get both players to point towards each other - sessionOwnerPlayer.Target = clientPlayer; - clientPlayer.Target = sessionOwnerPlayer; - - sessionOwnerPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward); - clientPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward); - - - yield return WaitForConditionOrTimeOut(() => sessionOwnerPlayer.HadContactWith(clientPlayer) || clientPlayer.HadContactWith(sessionOwnerPlayer)); - AssertOnTimeout("Timed out waiting for a player to collide with another player!"); - - clientPlayer.RegisterForContactEvents(false); - sessionOwnerPlayer.RegisterForContactEvents(false); - var otherPlayer = m_ContactEventType == ContactEventTypes.Default ? authority.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent() : - authority.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent(); - otherPlayer.RegisterForContactEvents(false); - otherPlayer = m_ContactEventType == ContactEventTypes.Default ? nonAuthority.SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent() : - nonAuthority.SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent(); - otherPlayer.RegisterForContactEvents(false); - - Object.Destroy(m_RigidbodyContactEventManager); - m_RigidbodyContactEventManager = null; - } + // Clean-up m_3DAuthorityInstance + if (m_3DAuthorityInstance) + { + Object.Destroy(m_3DAuthorityInstance); + m_3DAuthorityInstance = null; + } - protected override IEnumerator OnTearDown() - { - // In case of a test failure - if (m_RigidbodyContactEventManager) + // Clean-up m_2DAuthorityInstance + if (m_2DAuthorityInstance) { - Object.Destroy(m_RigidbodyContactEventManager); - m_RigidbodyContactEventManager = null; + Object.Destroy(m_2DAuthorityInstance); + m_2DAuthorityInstance = null; } return base.OnTearDown(); } } } -#endif // COM_UNITY_MODULES_PHYSICS +#endif // COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs new file mode 100644 index 0000000000..51aaa5ba56 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs @@ -0,0 +1,403 @@ +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D +using System.Collections; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + + +namespace Unity.Netcode.RuntimeTests +{ + + + [TestFixture(HostOrServer.Host, ContactEventTypes.Default)] + [TestFixture(HostOrServer.DAHost, ContactEventTypes.Default)] + [TestFixture(HostOrServer.Host, ContactEventTypes.WithInfo)] + [TestFixture(HostOrServer.DAHost, ContactEventTypes.WithInfo)] + internal class RigidbodyContactEventManagerTests : IntegrationTestWithApproximation + { + protected override int NumberOfClients => 1; + + private GameObject m_RigidbodyContactEventManager; + + public enum ContactEventTypes + { + Default, + WithInfo + } + + private ContactEventTypes m_ContactEventType; + private StringBuilder m_ErrorLogger = new StringBuilder(); + + public RigidbodyContactEventManagerTests(HostOrServer hostOrServer, ContactEventTypes contactEventType) : base(hostOrServer) + { + m_ContactEventType = contactEventType; + } + + protected override void OnCreatePlayerPrefab() + { + ContactEventTransformHelper.SessionOwnerSpawnPoint = GetRandomVector3(-4, -3); + ContactEventTransformHelper.ClientSpawnPoint = GetRandomVector3(3, 4); + if (m_ContactEventType == ContactEventTypes.Default) + { + var helper = m_PlayerPrefab.AddComponent(); + helper.AuthorityMode = NetworkTransform.AuthorityModes.Owner; + } + else + { + var helperWithInfo = m_PlayerPrefab.AddComponent(); + helperWithInfo.AuthorityMode = NetworkTransform.AuthorityModes.Owner; + } + + var rigidbody = m_PlayerPrefab.AddComponent(); + rigidbody.useGravity = false; + rigidbody.isKinematic = true; + rigidbody.mass = 5.0f; + rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous; + var sphereCollider = m_PlayerPrefab.AddComponent(); + sphereCollider.radius = 0.5f; + sphereCollider.providesContacts = true; + + var networkRigidbody = m_PlayerPrefab.AddComponent(); + networkRigidbody.UseRigidBodyForMotion = true; + networkRigidbody.AutoUpdateKinematicState = false; + + m_RigidbodyContactEventManager = new GameObject(); + m_RigidbodyContactEventManager.AddComponent(); + } + + + + private bool PlayersSpawnedInRightLocation() + { + var authority = GetAuthorityNetworkManager(); + var nonAuthority = GetNonAuthorityNetworkManager(); + + var position = authority.LocalClient.PlayerObject.transform.position; + if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, position)) + { + m_ErrorLogger.AppendLine($"Client-{authority.LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!"); + return false; + } + + position = nonAuthority.LocalClient.PlayerObject.transform.position; + if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position)) + { + m_ErrorLogger.AppendLine($"Client-{nonAuthority.LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!"); + return false; + } + var playerObject = (NetworkObject)null; + if (!authority.SpawnManager.SpawnedObjects.ContainsKey(nonAuthority.LocalClient.PlayerObject.NetworkObjectId)) + { + m_ErrorLogger.AppendLine($"Client-{authority.LocalClientId} cannot find a local spawned instance of Client-{nonAuthority.LocalClientId}'s player object!"); + return false; + } + playerObject = authority.SpawnManager.SpawnedObjects[nonAuthority.LocalClient.PlayerObject.NetworkObjectId]; + position = playerObject.transform.position; + + if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position)) + { + m_ErrorLogger.AppendLine($"Client-{authority.LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!"); + return false; + } + + if (!nonAuthority.SpawnManager.SpawnedObjects.ContainsKey(authority.LocalClient.PlayerObject.NetworkObjectId)) + { + m_ErrorLogger.AppendLine($"Client-{nonAuthority.LocalClientId} cannot find a local spawned instance of Client-{authority.LocalClientId}'s player object!"); + return false; + } + playerObject = nonAuthority.SpawnManager.SpawnedObjects[authority.LocalClient.PlayerObject.NetworkObjectId]; + position = playerObject.transform.position; + if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, playerObject.transform.position)) + { + m_ErrorLogger.AppendLine($"Client-{nonAuthority.LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!"); + return false; + } + return true; + } + + + [UnityTest] + public IEnumerator TestContactEvents() + { + ContactEventTransformHelper.VerboseDebug = m_EnableVerboseDebug; + + m_PlayerPrefab.SetActive(false); + m_ErrorLogger.Clear(); + // Validate all instances are spawned in the right location + yield return WaitForConditionOrTimeOut(PlayersSpawnedInRightLocation); + AssertOnTimeout($"Timed out waiting for all player instances to spawn in the corect location:\n {m_ErrorLogger}"); + m_ErrorLogger.Clear(); + + var authority = GetAuthorityNetworkManager(); + var nonAuthority = GetNonAuthorityNetworkManager(); + + var sessionOwnerPlayer = m_ContactEventType == ContactEventTypes.Default ? authority.LocalClient.PlayerObject.GetComponent() : + authority.LocalClient.PlayerObject.GetComponent(); + var clientPlayer = m_ContactEventType == ContactEventTypes.Default ? nonAuthority.LocalClient.PlayerObject.GetComponent() : + nonAuthority.LocalClient.PlayerObject.GetComponent(); + + // Get both players to point towards each other + sessionOwnerPlayer.Target = clientPlayer; + clientPlayer.Target = sessionOwnerPlayer; + + sessionOwnerPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward); + clientPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward); + + + yield return WaitForConditionOrTimeOut(() => sessionOwnerPlayer.HadContactWith(clientPlayer) || clientPlayer.HadContactWith(sessionOwnerPlayer)); + AssertOnTimeout("Timed out waiting for a player to collide with another player!"); + + clientPlayer.RegisterForContactEvents(false); + sessionOwnerPlayer.RegisterForContactEvents(false); + var otherPlayer = m_ContactEventType == ContactEventTypes.Default ? authority.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent() : + authority.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent(); + otherPlayer.RegisterForContactEvents(false); + otherPlayer = m_ContactEventType == ContactEventTypes.Default ? nonAuthority.SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent() : + nonAuthority.SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent(); + otherPlayer.RegisterForContactEvents(false); + + Object.Destroy(m_RigidbodyContactEventManager); + m_RigidbodyContactEventManager = null; + } + + protected override IEnumerator OnTearDown() + { + // In case of a test failure + if (m_RigidbodyContactEventManager) + { + Object.Destroy(m_RigidbodyContactEventManager); + m_RigidbodyContactEventManager = null; + } + + return base.OnTearDown(); + } + } + + #region Test helper classes + internal class ContactEventTransformHelperWithInfo : ContactEventTransformHelper, IContactEventHandlerWithInfo + { + public ContactEventHandlerInfo GetContactEventHandlerInfo() + { + var contactEventHandlerInfo = new ContactEventHandlerInfo() + { + HasContactEventPriority = IsOwner, + ProvideNonRigidBodyContactEvents = m_EnableNonRigidbodyContacts.Value, + }; + return contactEventHandlerInfo; + } + + protected override void OnRegisterForContactEvents(bool isRegistering) + { + RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering); + } + } + + internal class ContactEventTransformHelper : NetworkTransform, IContactEventHandler + { + public static Vector3 SessionOwnerSpawnPoint; + public static Vector3 ClientSpawnPoint; + public static bool VerboseDebug; + public enum HelperStates + { + None, + MoveForward, + } + + private HelperStates m_HelperState; + + public void SetHelperState(HelperStates state) + { + m_HelperState = state; + if (!m_NetworkRigidbody.IsKinematic()) + { + m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero; + m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero; + } + m_NetworkRigidbody.Rigidbody.isKinematic = m_HelperState == HelperStates.None; + if (!m_NetworkRigidbody.IsKinematic()) + { + m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero; + m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero; + } + + } + + protected struct ContactEventInfo + { + public ulong EventId; + public Vector3 AveragedCollisionNormal; + public Rigidbody CollidingBody; + public Vector3 ContactPoint; + } + + protected List m_ContactEvents = new List(); + + protected NetworkVariable m_EnableNonRigidbodyContacts = new NetworkVariable(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + + protected NetworkRigidbody m_NetworkRigidbody; + public ContactEventTransformHelper Target; + + public bool HasContactEvents() + { + return m_ContactEvents.Count > 0; + } + + public Rigidbody GetRigidbody() + { + return m_NetworkRigidbody.Rigidbody; + } + + public bool HadContactWith(ContactEventTransformHelper otherObject) + { + if (otherObject == null) + { + return false; + } + foreach (var contactEvent in m_ContactEvents) + { + if (contactEvent.CollidingBody == otherObject.m_NetworkRigidbody.Rigidbody) + { + return true; + } + } + return false; + } + + protected virtual void CheckToStopMoving() + { + SetHelperState(HadContactWith(Target) ? HelperStates.None : HelperStates.MoveForward); + } + + public void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default) + { + if (Target == null) + { + return; + } + + if (collidingBody != null) + { + Log($">>>>>>> contact event with {collidingBody.name}!"); + } + else + { + Log($">>>>>>> contact event with non-rigidbody!"); + } + + m_ContactEvents.Add(new ContactEventInfo() + { + EventId = eventId, + AveragedCollisionNormal = averagedCollisionNormal, + CollidingBody = collidingBody, + ContactPoint = contactPoint, + }); + CheckToStopMoving(); + } + + private void SetInitialPositionClientServer() + { + if (IsServer) + { + if (!NetworkManager.DistributedAuthorityMode && !IsLocalPlayer) + { + transform.position = ClientSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; + } + else + { + transform.position = SessionOwnerSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint; + } + } + else + { + transform.position = ClientSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; + } + } + + private void SetInitialPositionDistributedAuthority() + { + if (HasAuthority) + { + if (IsSessionOwner) + { + transform.position = SessionOwnerSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint; + } + else + { + transform.position = ClientSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; + } + } + } + + public override void OnNetworkSpawn() + { + m_NetworkRigidbody = GetComponent(); + + m_NetworkRigidbody.Rigidbody.maxLinearVelocity = 15; + m_NetworkRigidbody.Rigidbody.maxAngularVelocity = 10; + + if (NetworkManager.DistributedAuthorityMode) + { + SetInitialPositionDistributedAuthority(); + } + else + { + SetInitialPositionClientServer(); + } + if (IsLocalPlayer) + { + RegisterForContactEvents(true); + } + else + { + m_NetworkRigidbody.Rigidbody.detectCollisions = false; + } + base.OnNetworkSpawn(); + } + + protected virtual void OnRegisterForContactEvents(bool isRegistering) + { + RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering); + } + + public void RegisterForContactEvents(bool isRegistering) + { + OnRegisterForContactEvents(isRegistering); + } + + private void FixedUpdate() + { + if (!IsSpawned || !IsOwner || m_HelperState != HelperStates.MoveForward) + { + return; + } + var distance = Vector3.Distance(Target.transform.position, transform.position); + var moveAmount = Mathf.Max(1.2f, distance); + // Head towards our target + var dir = (Target.transform.position - transform.position).normalized; + var deltaMove = dir * moveAmount * Time.fixedDeltaTime; + m_NetworkRigidbody.Rigidbody.MovePosition(m_NetworkRigidbody.Rigidbody.position + deltaMove); + + + Log($" Loc: {transform.position} | Dest: {Target.transform.position} | Dist: {distance} | MoveDelta: {deltaMove}"); + } + + protected void Log(string msg) + { + if (VerboseDebug) + { + Debug.Log($"Client-{OwnerClientId} {msg}"); + } + } + } + #endregion +} +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs.meta new file mode 100644 index 0000000000..687995a482 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6d002e232abdb5049aa3c4f5cc36292d \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs new file mode 100644 index 0000000000..0c5895b612 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs @@ -0,0 +1,97 @@ +using System.Collections; +using System.Text.RegularExpressions; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.DAHost)] + internal class NetworkPrefabHandlerSynchronizationTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + + public NetworkPrefabHandlerSynchronizationTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + private GameObject m_ValidPrefab; + private GameObject m_ClientSideValidPrefab; + private GameObject m_ClientSideExceptionPrefab; + + protected override void OnServerAndClientsCreated() + { + m_ValidPrefab = CreateNetworkObjectPrefab("ValidPrefab"); + m_ClientSideValidPrefab = CreateNetworkObjectPrefab("ClientSideValidPrefab"); + m_ClientSideExceptionPrefab = CreateNetworkObjectPrefab("ClientSideExceptionPrefab"); + base.OnServerAndClientsCreated(); + } + + [UnityTest] + [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer, RuntimePlatform.OSXPlayer, RuntimePlatform.OSXEditor })] // Ignored test tracked in MTT-15473 + public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() + { + var nonAuthority = GetNonAuthorityNetworkManager(); + + var networkObjectToSpawnOnClient = m_ClientSideValidPrefab.GetComponent(); + nonAuthority.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, new NetworkPrefabExceptionThrower()); + nonAuthority.PrefabHandler.AddHandler(m_ValidPrefab, new NetworkPrefabInstanceHandler(networkObjectToSpawnOnClient)); + + var authority = GetAuthorityNetworkManager(); + + // Spawn the invalid object first. + var exceptionObject = SpawnObject(m_ClientSideExceptionPrefab, authority).GetComponent(); + + // Check the invalid object spawns on the authority, expect an error from non-authority. + LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); + LogAssert.Expect(LogType.Error, new Regex("Failed to spawn NetworkObject!")); + // Authority should receive an error from non-authority and should use the globalObjectIdHash to find the failing object + LogAssert.Expect(LogType.Error, new Regex($@"SenderId:{nonAuthority.LocalClientId}\]\[{Regex.Escape(exceptionObject.name)}")); + + yield return WaitForConditionOrTimeOut(() => exceptionObject.IsSpawned); + AssertOnTimeout("Failed to spawn object on authority!"); + + // Now spawn a valid object + var validObject = SpawnObject(m_ValidPrefab, authority).GetComponent(); + + // The valid object should spawn as expected + yield return WaitForSpawnedOnAllOrTimeOut(validObject); + AssertOnTimeout("Failed to spawn valid prefab on all clients!"); + + // Create a new client and register the same PrefabHandlers on the client + var newClient = CreateNewClient(); + newClient.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, new NetworkPrefabExceptionThrower()); + newClient.PrefabHandler.AddHandler(m_ValidPrefab, new NetworkPrefabInstanceHandler(networkObjectToSpawnOnClient)); + + // Expect assertions from the new client + LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); + LogAssert.Expect(LogType.Error, new Regex("Failed to spawn NetworkObject!")); + + // Authority will receive an error from new client and should use the globalObjectIdHash to find the failing object + var expectedNewClientId = nonAuthority.LocalClientId + 1; + LogAssert.Expect(LogType.Error, new Regex($@"SenderId:{expectedNewClientId}\]\[{Regex.Escape(exceptionObject.name)}")); + + // Start and synchronize the new client + yield return StartClient(newClient); + + // Validate the valid prefab spawned on all clients without issue + var expectedAuthorityHash = m_ValidPrefab.GetComponent().GlobalObjectIdHash; + var expectedNonAuthorityHash = m_ClientSideValidPrefab.GetComponent().GlobalObjectIdHash; + foreach (var networkManager in m_NetworkManagers) + { + Assert.True(networkManager.SpawnManager.SpawnedObjects.TryGetValue(validObject.NetworkObjectId, out NetworkObject spawnedObject), $"Client-{networkManager.LocalClientId} failed to spawn version of valid object!"); + + if (spawnedObject.HasAuthority) + { + Assert.That(spawnedObject.GlobalObjectIdHash, Is.EqualTo(expectedAuthorityHash), "NetworkObject spawned with unexpected GlobalObjectIdHash!"); + Assert.That(networkManager.SpawnManager.SpawnedObjects.ContainsKey(exceptionObject.NetworkObjectId), Is.True, "Authority missing spawned NetworkObject!"); + } + else + { + Assert.That(spawnedObject.GlobalObjectIdHash, Is.EqualTo(expectedNonAuthorityHash), "NetworkObject spawned with unexpected GlobalObjectIdHash!"); + Assert.That(networkManager.SpawnManager.SpawnedObjects.ContainsKey(exceptionObject.NetworkObjectId), Is.False, "Non authority should not have spawned exception object!"); + } + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs.meta new file mode 100644 index 0000000000..a0c417d419 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 63f7e166459749bfbbf88e5b82ab917d +timeCreated: 1783026731 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index 82abbe2fd2..497fbf50df 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -2024,6 +2024,22 @@ protected IEnumerator WaitForConditionOrTimeOut(Func checkF }, timeOutHelper); } + /// + /// Waits until the specified condition returns true or a timeout occurs. + /// This overload allows the condition to provide additional error details via a . + /// + /// A delegate that takes a for error details and returns true when the desired condition is met. + /// the maximum times to check for the condition (default is 60). + protected void WaitForConditionOrTimeOutWithTimeTravel(Func checkForCondition, int maxTries = 60) + { + WaitForConditionOrTimeOutWithTimeTravel(() => + { + // Clear errorBuilder before each check to ensure the errorBuilder only contains information from the lastest run + m_InternalErrorLog.Clear(); + return checkForCondition(m_InternalErrorLog); + }, maxTries); + } + /// /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. /// @@ -2078,7 +2094,7 @@ protected IEnumerator WaitForSpawnedOnAllOrTimeOut(GameObject gameObject, Timeou /// The list of s to wait for. /// An optional to control the timeout period. If null, the default timeout is used. /// An for use in Unity coroutines. - protected IEnumerator WaitForSpawnedOnAllOrTimeOut(List networkObjects, TimeoutHelper timeOutHelper = null) + protected IEnumerator WaitForSpawnedOnAllOrTimeOut(ICollection networkObjects, TimeoutHelper timeOutHelper = null) { bool ValidateObjectsSpawnedOnAllClients(StringBuilder errorLog) { @@ -2099,6 +2115,77 @@ bool ValidateObjectsSpawnedOnAllClients(StringBuilder errorLog) yield return WaitForConditionOrTimeOut(ValidateObjectsSpawnedOnAllClients, timeOutHelper); } + /// + /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. + /// + /// The id of the to wait for. + /// the maximum times to check for the condition (default is 60). + protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(ulong networkObjectId, int maxTries = 60) + { + bool ValidateObjectSpawnedOnAllClients(StringBuilder errorLog) + { + foreach (var client in m_NetworkManagers) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + { + errorLog.Append($"Client-{client.LocalClientId} has not spawned Object-{networkObjectId}!"); + return false; + } + } + return true; + } + + WaitForConditionOrTimeOutWithTimeTravel(ValidateObjectSpawnedOnAllClients, maxTries); + } + + /// + /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. + /// + /// The to wait for. + /// the maximum times to check for the condition (default is 60). + protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(NetworkObject networkObject, int maxTries = 60) + { + var networkObjectId = networkObject.NetworkObjectId; + WaitForSpawnedOnAllOrTimeOutWithTimeTravel(networkObjectId, maxTries); + } + + /// + /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. + /// + /// The containing a to wait for. + /// the maximum times to check for the condition (default is 60). + protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(GameObject gameObject, int maxTries = 60) + { + var networkObjectId = gameObject.GetComponent().NetworkObjectId; + WaitForSpawnedOnAllOrTimeOutWithTimeTravel(networkObjectId, maxTries); + } + + /// + /// Waits until all given NetworkObjects are spawned on all clients or a timeout occurs. + /// + /// The list of s to wait for. + /// the maximum times to check for the condition (default is 60). + protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(List networkObjects, int maxTries = 60) + { + bool ValidateObjectsSpawnedOnAllClients(StringBuilder errorLog) + { + foreach (var client in m_NetworkManagers) + { + foreach (var networkObject in networkObjects) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(networkObject.NetworkObjectId)) + { + errorLog.Append($"Client-{client.LocalClientId} has not spawned Object-{networkObject.NetworkObjectId}!"); + return false; + } + } + } + return true; + } + + WaitForConditionOrTimeOutWithTimeTravel(ValidateObjectsSpawnedOnAllClients, maxTries); + } + /// /// Waits until all given NetworkObjects are spawned on all clients or a timeout occurs. /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeLogAssert.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeLogAssert.cs index 72f9753ee1..e2c95d7450 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeLogAssert.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeLogAssert.cs @@ -222,6 +222,30 @@ public bool HasLogBeenReceived(LogType type, string message) return found; } + /// + /// Determines if a log message was logged or not. + /// + /// to check for. + /// containing the message to search for. + /// or + public bool HasLogBeenReceived(LogType type, Regex message) + { + var found = false; + lock (m_Lock) + { + foreach (var logEvent in AllLogs) + { + if (logEvent.LogType == type && message.IsMatch(logEvent.Message)) + { + found = true; + break; + } + } + } + return found; + } + + /// /// Clears out the log history that is searched. /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs index abbfcad948..2bdce8f205 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs @@ -429,7 +429,10 @@ public IEnumerator ServerDisconnectWithDataInQueue() } // Check client disconnection with data in send queue. - [UnityTest] + // Excluded on iOS: sending Unreliable data then immediately disconnecting is a race on slow + // iOS CI devices (the unreliable packet can be dropped), so it flakes with a "Timed out while + // waiting for network event" even after raising the mobile timeout. Tracked by MTT-15433. + [UnityTest, UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer })] public IEnumerator ClientDisconnectWithDataInQueue() { InitializeTransport(out m_Server, out m_ServerEvents); diff --git a/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset b/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset index 6a1c39a006..69bec9b722 100644 --- a/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset +++ b/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset @@ -15,7 +15,8 @@ MonoBehaviour: m_DefaultGroup: 93aa504d1b753cb41a8a779ae63f5795 m_currentHash: serializedVersion: 2 - Hash: c105cac3e36a87a6dabc4f3755bc1fd9 + Hash: 00000000000000000000000000000000 + m_ExtractTypeTreeData: 0 m_OptimizeCatalogSize: 0 m_BuildRemoteCatalog: 0 m_CatalogRequestsTimeout: 0 @@ -33,6 +34,7 @@ MonoBehaviour: m_UniqueBundleIds: 0 m_EnableJsonCatalog: 0 m_NonRecursiveBuilding: 1 + m_AllowNestedBundleFolders: 0 m_CCDEnabled: 0 m_maxConcurrentWebRequests: 500 m_UseUWRForLocalBundles: 0 @@ -41,6 +43,7 @@ MonoBehaviour: m_BundleRedirectLimit: -1 m_SharedBundleSettings: 0 m_SharedBundleSettingsCustomGroupIndex: 0 + m_simulatedLoadDelay: 0.1 m_ContiguousBundles: 0 m_StripUnityVersionFromBundleBuild: 0 m_DisableVisibleSubAssetRepresentations: 0 @@ -70,32 +73,32 @@ MonoBehaviour: m_Id: b7ba5fc73af2a49449023b732cdf652d m_ProfileName: Default m_Values: - - m_Id: a2553e7788fb43741926b3128a0aa641 - m_Value: '[UnityEditor.EditorUserBuildSettings.activeBuildTarget]' - - m_Id: 8bc200e556c0a2f4daf1a4696958eaa6 - m_Value: '[UnityEngine.AddressableAssets.Addressables.BuildPath]/[BuildTarget]' + - m_Id: 2a3d80e942fdbfe49a979d4a22bbe893 + m_Value: 'http://localhost/[BuildTarget]' - m_Id: 80085c797452bb94ca0bf0a4b2ec258c m_Value: '{UnityEngine.AddressableAssets.Addressables.RuntimePath}/[BuildTarget]' + - m_Id: 8bc200e556c0a2f4daf1a4696958eaa6 + m_Value: '[UnityEngine.AddressableAssets.Addressables.BuildPath]/[BuildTarget]' + - m_Id: a2553e7788fb43741926b3128a0aa641 + m_Value: '[UnityEditor.EditorUserBuildSettings.activeBuildTarget]' - m_Id: eaae5cc67c56cfe4299363a742a284b3 m_Value: 'ServerData/[BuildTarget]' - - m_Id: 2a3d80e942fdbfe49a979d4a22bbe893 - m_Value: 'http://localhost/[BuildTarget]' m_ProfileEntryNames: - - m_Id: a2553e7788fb43741926b3128a0aa641 - m_Name: BuildTarget + - m_Id: 2a3d80e942fdbfe49a979d4a22bbe893 + m_Name: Remote.LoadPath + m_InlineUsage: 0 + - m_Id: 80085c797452bb94ca0bf0a4b2ec258c + m_Name: Local.LoadPath m_InlineUsage: 0 - m_Id: 8bc200e556c0a2f4daf1a4696958eaa6 m_Name: Local.BuildPath m_InlineUsage: 0 - - m_Id: 80085c797452bb94ca0bf0a4b2ec258c - m_Name: Local.LoadPath + - m_Id: a2553e7788fb43741926b3128a0aa641 + m_Name: BuildTarget m_InlineUsage: 0 - m_Id: eaae5cc67c56cfe4299363a742a284b3 m_Name: Remote.BuildPath m_InlineUsage: 0 - - m_Id: 2a3d80e942fdbfe49a979d4a22bbe893 - m_Name: Remote.LoadPath - m_InlineUsage: 0 m_ProfileVersion: 1 m_LabelTable: m_LabelNames: diff --git a/testproject/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset b/testproject/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset index 5d957b4c4e..36d70b4b1c 100644 --- a/testproject/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset +++ b/testproject/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset @@ -13,19 +13,16 @@ MonoBehaviour: m_Name: Default Local Group m_EditorClassIdentifier: m_GroupName: Default Local Group - m_Data: - m_SerializedData: [] m_GUID: 93aa504d1b753cb41a8a779ae63f5795 m_SerializeEntries: - m_GUID: ffa1ab8ed58b72343ad93116ded1700a m_Address: AddressableTestObject.prefab m_ReadOnly: 0 m_SerializedLabels: [] - m_MainAsset: {fileID: 0} - m_TargetAsset: {fileID: 0} + FlaggedDuringContentUpdateRestriction: 0 m_ReadOnly: 0 m_Settings: {fileID: 11400000, guid: 75e5cd8b6bfca5d49a5818e70d71b64d, type: 2} m_SchemaSet: m_Schemas: - - {fileID: 11400000, guid: 6b8a226e7c996ad4b841b997672f866c, type: 2} - {fileID: 11400000, guid: 07e6ff907f5779a42b6e632340d10c58, type: 2} + - {fileID: 11400000, guid: 6b8a226e7c996ad4b841b997672f866c, type: 2} diff --git a/testproject/Assets/Prefabs/AddressableTestObject.prefab b/testproject/Assets/Prefabs/AddressableTestObject.prefab index 473b199037..2930d6b0f3 100644 --- a/testproject/Assets/Prefabs/AddressableTestObject.prefab +++ b/testproject/Assets/Prefabs/AddressableTestObject.prefab @@ -9,8 +9,8 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 6175900562428407388} - - component: {fileID: 6175900562428407389} - component: {fileID: 6175900562428407386} + - component: {fileID: 6175900562428407389} m_Layer: 0 m_Name: AddressableTestObject m_TagString: Untagged @@ -25,14 +25,15 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6175900562428407387} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &6175900562428407389 +--- !u!114 &6175900562428407386 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -41,12 +42,24 @@ MonoBehaviour: m_GameObject: {fileID: 6175900562428407387} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 6c8e5a8a448245769b69af7de9c47b21, type: 3} + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - AnIntVal: 1234567 - AStringVal: 1234567 ---- !u!114 &6175900562428407386 + GlobalObjectIdHash: 4054942115 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + k__BackingField: 0 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 +--- !u!114 &6175900562428407389 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -55,10 +68,9 @@ MonoBehaviour: m_GameObject: {fileID: 6175900562428407387} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Script: {fileID: 11500000, guid: 6c8e5a8a448245769b69af7de9c47b21, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 951099334 - AlwaysReplicateAsRoot: 0 - DontDestroyWithOwner: 0 - AutoObjectParentSync: 1 + ShowTopMostFoldoutHeaderGroup: 1 + AnIntVal: 1234567 + AStringVal: 1234567 diff --git a/testproject/Assets/Scenes/AddressableInSceneObject.unity b/testproject/Assets/Scenes/AddressableInSceneObject.unity new file mode 100644 index 0000000000..e9fea46774 --- /dev/null +++ b/testproject/Assets/Scenes/AddressableInSceneObject.unity @@ -0,0 +1,199 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1001 &1879396995 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 6175900562428407386, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: GlobalObjectIdHash + value: 1346653293 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407387, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_Name + value: AddressableTestObject + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalPosition.x + value: 1.09747 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalPosition.z + value: 1.59747 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: ffa1ab8ed58b72343ad93116ded1700a, type: 3} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1879396995} diff --git a/testproject/Assets/Scenes/AddressableInSceneObject.unity.meta b/testproject/Assets/Scenes/AddressableInSceneObject.unity.meta new file mode 100644 index 0000000000..dbdfd8aca5 --- /dev/null +++ b/testproject/Assets/Scenes/AddressableInSceneObject.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 68d21678646384e6291bb2b568b5d95c +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: addressablebundle + assetBundleVariant: diff --git a/testproject/Assets/Scenes/MultiprocessTestScene.unity b/testproject/Assets/Scenes/MultiprocessTestScene.unity deleted file mode 100644 index f67263407e..0000000000 --- a/testproject/Assets/Scenes/MultiprocessTestScene.unity +++ /dev/null @@ -1,1062 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!29 &1 -OcclusionCullingSettings: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_OcclusionBakeSettings: - smallestOccluder: 5 - smallestHole: 0.25 - backfaceThreshold: 100 - m_SceneGUID: 00000000000000000000000000000000 - m_OcclusionCullingData: {fileID: 0} ---- !u!104 &2 -RenderSettings: - m_ObjectHideFlags: 0 - serializedVersion: 10 - m_Fog: 0 - m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} - m_FogMode: 3 - m_FogDensity: 0.01 - m_LinearFogStart: 0 - m_LinearFogEnd: 300 - m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} - m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} - m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} - m_AmbientIntensity: 1 - m_AmbientMode: 0 - m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} - m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} - m_HaloStrength: 0.5 - m_FlareStrength: 1 - m_FlareFadeSpeed: 3 - m_HaloTexture: {fileID: 0} - m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} - m_DefaultReflectionMode: 0 - m_DefaultReflectionResolution: 128 - m_ReflectionBounces: 1 - m_ReflectionIntensity: 1 - m_CustomReflection: {fileID: 0} - m_Sun: {fileID: 0} - m_UseRadianceAmbientProbe: 0 ---- !u!157 &3 -LightmapSettings: - m_ObjectHideFlags: 0 - serializedVersion: 13 - m_BakeOnSceneLoad: 0 - m_GISettings: - serializedVersion: 2 - m_BounceScale: 1 - m_IndirectOutputScale: 1 - m_AlbedoBoost: 1 - m_EnvironmentLightingMode: 0 - m_EnableBakedLightmaps: 1 - m_EnableRealtimeLightmaps: 0 - m_LightmapEditorSettings: - serializedVersion: 12 - m_Resolution: 2 - m_BakeResolution: 40 - m_AtlasSize: 1024 - m_AO: 0 - m_AOMaxDistance: 1 - m_CompAOExponent: 1 - m_CompAOExponentDirect: 0 - m_ExtractAmbientOcclusion: 0 - m_Padding: 2 - m_LightmapParameters: {fileID: 0} - m_LightmapsBakeMode: 1 - m_TextureCompression: 1 - m_ReflectionCompression: 2 - m_MixedBakeMode: 2 - m_BakeBackend: 1 - m_PVRSampling: 1 - m_PVRDirectSampleCount: 32 - m_PVRSampleCount: 512 - m_PVRBounces: 2 - m_PVREnvironmentSampleCount: 256 - m_PVREnvironmentReferencePointCount: 2048 - m_PVRFilteringMode: 1 - m_PVRDenoiserTypeDirect: 1 - m_PVRDenoiserTypeIndirect: 1 - m_PVRDenoiserTypeAO: 1 - m_PVRFilterTypeDirect: 0 - m_PVRFilterTypeIndirect: 0 - m_PVRFilterTypeAO: 0 - m_PVREnvironmentMIS: 1 - m_PVRCulling: 1 - m_PVRFilteringGaussRadiusDirect: 1 - m_PVRFilteringGaussRadiusIndirect: 5 - m_PVRFilteringGaussRadiusAO: 2 - m_PVRFilteringAtrousPositionSigmaDirect: 0.5 - m_PVRFilteringAtrousPositionSigmaIndirect: 2 - m_PVRFilteringAtrousPositionSigmaAO: 1 - m_ExportTrainingData: 0 - m_TrainingDataDestination: TrainingData - m_LightProbeSampleCountMultiplier: 4 - m_LightingDataAsset: {fileID: 0} - m_LightingSettings: {fileID: 130932425} ---- !u!196 &4 -NavMeshSettings: - serializedVersion: 2 - m_ObjectHideFlags: 0 - m_BuildSettings: - serializedVersion: 3 - agentTypeID: 0 - agentRadius: 0.5 - agentHeight: 2 - agentSlope: 45 - agentClimb: 0.4 - ledgeDropHeight: 0 - maxJumpAcrossDistance: 0 - minRegionArea: 2 - manualCellSize: 0 - cellSize: 0.16666667 - manualTileSize: 0 - tileSize: 256 - buildHeightMesh: 0 - maxJobWorkers: 0 - preserveTilesOutsideBounds: 0 - debug: - m_Flags: 0 - m_NavMeshData: {fileID: 0} ---- !u!1 &127222500 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 127222502} - - component: {fileID: 127222501} - m_Layer: 0 - m_Name: Directional Light - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!108 &127222501 -Light: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 127222500} - m_Enabled: 1 - serializedVersion: 11 - m_Type: 1 - m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} - m_Intensity: 1 - m_Range: 10 - m_SpotAngle: 30 - m_InnerSpotAngle: 21.80208 - m_CookieSize: 10 - m_Shadows: - m_Type: 2 - m_Resolution: -1 - m_CustomResolution: -1 - m_Strength: 1 - m_Bias: 0.05 - m_NormalBias: 0.4 - m_NearPlane: 0.2 - m_CullingMatrixOverride: - e00: 1 - e01: 0 - e02: 0 - e03: 0 - e10: 0 - e11: 1 - e12: 0 - e13: 0 - e20: 0 - e21: 0 - e22: 1 - e23: 0 - e30: 0 - e31: 0 - e32: 0 - e33: 1 - m_UseCullingMatrixOverride: 0 - m_Cookie: {fileID: 0} - m_DrawHalo: 0 - m_Flare: {fileID: 0} - m_RenderMode: 0 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingLayerMask: 1 - m_Lightmapping: 4 - m_LightShadowCasterMode: 0 - m_AreaSize: {x: 1, y: 1} - m_BounceIntensity: 1 - m_ColorTemperature: 6570 - m_UseColorTemperature: 0 - m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} - m_UseBoundingSphereOverride: 0 - m_UseViewFrustumForShadowCasterCull: 1 - m_ForceVisible: 0 - m_ShadowRadius: 0 - m_ShadowAngle: 0 - m_LightUnit: 1 - m_LuxAtDistance: 1 - m_EnableSpotReflector: 1 ---- !u!4 &127222502 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 127222500} - serializedVersion: 2 - m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} - m_LocalPosition: {x: 0, y: 3, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} ---- !u!850595691 &130932425 -LightingSettings: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: - serializedVersion: 9 - m_EnableBakedLightmaps: 0 - m_EnableRealtimeLightmaps: 0 - m_RealtimeEnvironmentLighting: 1 - m_BounceScale: 1 - m_AlbedoBoost: 1 - m_IndirectOutputScale: 1 - m_UsingShadowmask: 1 - m_BakeBackend: 1 - m_LightmapMaxSize: 1024 - m_LightmapSizeFixed: 0 - m_UseMipmapLimits: 1 - m_BakeResolution: 40 - m_Padding: 2 - m_LightmapCompression: 3 - m_AO: 0 - m_AOMaxDistance: 1 - m_CompAOExponent: 1 - m_CompAOExponentDirect: 0 - m_ExtractAO: 0 - m_MixedBakeMode: 2 - m_LightmapsBakeMode: 1 - m_FilterMode: 1 - m_LightmapParameters: {fileID: 15204, guid: 0000000000000000f000000000000000, type: 0} - m_ExportTrainingData: 0 - m_EnableWorkerProcessBaking: 1 - m_TrainingDataDestination: TrainingData - m_RealtimeResolution: 2 - m_ForceWhiteAlbedo: 0 - m_ForceUpdates: 0 - m_PVRCulling: 1 - m_PVRSampling: 1 - m_PVRDirectSampleCount: 32 - m_PVRSampleCount: 512 - m_PVREnvironmentSampleCount: 256 - m_PVREnvironmentReferencePointCount: 2048 - m_LightProbeSampleCountMultiplier: 4 - m_PVRBounces: 2 - m_PVRMinBounces: 2 - m_PVREnvironmentImportanceSampling: 1 - m_PVRFilteringMode: 1 - m_PVRDenoiserTypeDirect: 1 - m_PVRDenoiserTypeIndirect: 1 - m_PVRDenoiserTypeAO: 1 - m_PVRFilterTypeDirect: 0 - m_PVRFilterTypeIndirect: 0 - m_PVRFilterTypeAO: 0 - m_PVRFilteringGaussRadiusDirect: 1 - m_PVRFilteringGaussRadiusIndirect: 5 - m_PVRFilteringGaussRadiusAO: 2 - m_PVRFilteringAtrousPositionSigmaDirect: 0.5 - m_PVRFilteringAtrousPositionSigmaIndirect: 2 - m_PVRFilteringAtrousPositionSigmaAO: 1 - m_RespectSceneVisibilityWhenBakingGI: 0 ---- !u!1 &160940364 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 160940368} - - component: {fileID: 160940367} - - component: {fileID: 160940366} - - component: {fileID: 160940365} - m_Layer: 0 - m_Name: Boundary bottom left - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!135 &160940365 -SphereCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 160940364} - m_Material: {fileID: 0} - m_IncludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_ExcludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_LayerOverridePriority: 0 - m_IsTrigger: 0 - m_ProvidesContacts: 0 - m_Enabled: 1 - serializedVersion: 3 - m_Radius: 0.5 - m_Center: {x: 0, y: 0, z: 0} ---- !u!23 &160940366 -MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 160940364} - m_Enabled: 1 - m_CastShadows: 1 - m_ReceiveShadows: 1 - m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 - m_MotionVectors: 1 - m_LightProbeUsage: 1 - m_ReflectionProbeUsage: 1 - m_RayTracingMode: 2 - m_RayTraceProcedural: 0 - m_RayTracingAccelStructBuildFlagsOverride: 0 - m_RayTracingAccelStructBuildFlags: 1 - m_SmallMeshCulling: 1 - m_RenderingLayerMask: 1 - m_RendererPriority: 0 - m_Materials: - - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} - m_StaticBatchInfo: - firstSubMesh: 0 - subMeshCount: 0 - m_StaticBatchRoot: {fileID: 0} - m_ProbeAnchor: {fileID: 0} - m_LightProbeVolumeOverride: {fileID: 0} - m_ScaleInLightmap: 1 - m_ReceiveGI: 1 - m_PreserveUVs: 0 - m_IgnoreNormalsForChartDetection: 0 - m_ImportantGI: 0 - m_StitchLightmapSeams: 1 - m_SelectedEditorRenderState: 3 - m_MinimumChartSize: 4 - m_AutoUVMaxDistance: 0.5 - m_AutoUVMaxAngle: 89 - m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 - m_SortingOrder: 0 - m_AdditionalVertexStreams: {fileID: 0} ---- !u!33 &160940367 -MeshFilter: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 160940364} - m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} ---- !u!4 &160940368 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 160940364} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -10, y: -10, z: -10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &430011403 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 430011407} - - component: {fileID: 430011406} - - component: {fileID: 430011405} - - component: {fileID: 430011404} - m_Layer: 0 - m_Name: ThreeDText - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &430011404 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 430011403} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 04cf3cc1396054b009a1ed283aa50021, type: 3} - m_Name: - m_EditorClassIdentifier: - IsTestCoordinatorActiveAndEnabled: 0 - CommandLineArguments: ---- !u!102 &430011405 -TextMesh: - serializedVersion: 3 - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 430011403} - m_Text: Hello World - m_OffsetZ: 0 - m_CharacterSize: 1 - m_LineSpacing: 1 - m_Anchor: 0 - m_Alignment: 0 - m_TabSize: 4 - m_FontSize: 0 - m_FontStyle: 0 - m_RichText: 1 - m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} - m_Color: - serializedVersion: 2 - rgba: 4294967295 ---- !u!23 &430011406 -MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 430011403} - m_Enabled: 1 - m_CastShadows: 1 - m_ReceiveShadows: 1 - m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 - m_MotionVectors: 1 - m_LightProbeUsage: 1 - m_ReflectionProbeUsage: 1 - m_RayTracingMode: 2 - m_RayTraceProcedural: 0 - m_RayTracingAccelStructBuildFlagsOverride: 0 - m_RayTracingAccelStructBuildFlags: 1 - m_SmallMeshCulling: 1 - m_RenderingLayerMask: 1 - m_RendererPriority: 0 - m_Materials: - - {fileID: 10100, guid: 0000000000000000e000000000000000, type: 0} - m_StaticBatchInfo: - firstSubMesh: 0 - subMeshCount: 0 - m_StaticBatchRoot: {fileID: 0} - m_ProbeAnchor: {fileID: 0} - m_LightProbeVolumeOverride: {fileID: 0} - m_ScaleInLightmap: 1 - m_ReceiveGI: 1 - m_PreserveUVs: 0 - m_IgnoreNormalsForChartDetection: 0 - m_ImportantGI: 0 - m_StitchLightmapSeams: 1 - m_SelectedEditorRenderState: 3 - m_MinimumChartSize: 4 - m_AutoUVMaxDistance: 0.5 - m_AutoUVMaxAngle: 89 - m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 - m_SortingOrder: 0 - m_AdditionalVertexStreams: {fileID: 0} ---- !u!4 &430011407 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 430011403} - serializedVersion: 2 - m_LocalRotation: {x: 0.2164396, y: 0, z: 0, w: 0.97629607} - m_LocalPosition: {x: -45, y: 10, z: 10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 25, y: 0, z: 0} ---- !u!1 &941021721 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 941021724} - - component: {fileID: 941021723} - - component: {fileID: 941021722} - m_Layer: 0 - m_Name: Main Camera - m_TagString: MainCamera - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!81 &941021722 -AudioListener: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 941021721} - m_Enabled: 1 ---- !u!20 &941021723 -Camera: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 941021721} - m_Enabled: 1 - serializedVersion: 2 - m_ClearFlags: 1 - m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} - m_projectionMatrixMode: 1 - m_GateFitMode: 2 - m_FOVAxisMode: 0 - m_Iso: 200 - m_ShutterSpeed: 0.005 - m_Aperture: 16 - m_FocusDistance: 10 - m_FocalLength: 50 - m_BladeCount: 5 - m_Curvature: {x: 2, y: 11} - m_BarrelClipping: 0.25 - m_Anamorphism: 0 - m_SensorSize: {x: 36, y: 24} - m_LensShift: {x: 0, y: 0} - m_NormalizedViewPortRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 1 - height: 1 - near clip plane: 0.3 - far clip plane: 1000 - field of view: 60 - orthographic: 0 - orthographic size: 5 - m_Depth: -1 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingPath: -1 - m_TargetTexture: {fileID: 0} - m_TargetDisplay: 0 - m_TargetEye: 3 - m_HDR: 1 - m_AllowMSAA: 1 - m_AllowDynamicResolution: 0 - m_ForceIntoRT: 0 - m_OcclusionCulling: 1 - m_StereoConvergence: 10 - m_StereoSeparation: 0.022 ---- !u!4 &941021724 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 941021721} - serializedVersion: 2 - m_LocalRotation: {x: 0.21736304, y: -0, z: -0, w: 0.97609085} - m_LocalPosition: {x: 0, y: 9.15, z: -27.5} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 25.108, y: 0, z: 0} ---- !u!1 &996484657 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 996484661} - - component: {fileID: 996484660} - - component: {fileID: 996484659} - - component: {fileID: 996484658} - m_Layer: 0 - m_Name: Boundary center - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!135 &996484658 -SphereCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 996484657} - m_Material: {fileID: 0} - m_IncludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_ExcludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_LayerOverridePriority: 0 - m_IsTrigger: 0 - m_ProvidesContacts: 0 - m_Enabled: 1 - serializedVersion: 3 - m_Radius: 0.5 - m_Center: {x: 0, y: 0, z: 0} ---- !u!23 &996484659 -MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 996484657} - m_Enabled: 1 - m_CastShadows: 1 - m_ReceiveShadows: 1 - m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 - m_MotionVectors: 1 - m_LightProbeUsage: 1 - m_ReflectionProbeUsage: 1 - m_RayTracingMode: 2 - m_RayTraceProcedural: 0 - m_RayTracingAccelStructBuildFlagsOverride: 0 - m_RayTracingAccelStructBuildFlags: 1 - m_SmallMeshCulling: 1 - m_RenderingLayerMask: 1 - m_RendererPriority: 0 - m_Materials: - - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} - m_StaticBatchInfo: - firstSubMesh: 0 - subMeshCount: 0 - m_StaticBatchRoot: {fileID: 0} - m_ProbeAnchor: {fileID: 0} - m_LightProbeVolumeOverride: {fileID: 0} - m_ScaleInLightmap: 1 - m_ReceiveGI: 1 - m_PreserveUVs: 0 - m_IgnoreNormalsForChartDetection: 0 - m_ImportantGI: 0 - m_StitchLightmapSeams: 1 - m_SelectedEditorRenderState: 3 - m_MinimumChartSize: 4 - m_AutoUVMaxDistance: 0.5 - m_AutoUVMaxAngle: 89 - m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 - m_SortingOrder: 0 - m_AdditionalVertexStreams: {fileID: 0} ---- !u!33 &996484660 -MeshFilter: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 996484657} - m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} ---- !u!4 &996484661 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 996484657} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &1206022453 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1206022457} - - component: {fileID: 1206022456} - - component: {fileID: 1206022455} - - component: {fileID: 1206022454} - m_Layer: 0 - m_Name: Boundary top right - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!135 &1206022454 -SphereCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1206022453} - m_Material: {fileID: 0} - m_IncludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_ExcludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_LayerOverridePriority: 0 - m_IsTrigger: 0 - m_ProvidesContacts: 0 - m_Enabled: 1 - serializedVersion: 3 - m_Radius: 0.5 - m_Center: {x: 0, y: 0, z: 0} ---- !u!23 &1206022455 -MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1206022453} - m_Enabled: 1 - m_CastShadows: 1 - m_ReceiveShadows: 1 - m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 - m_MotionVectors: 1 - m_LightProbeUsage: 1 - m_ReflectionProbeUsage: 1 - m_RayTracingMode: 2 - m_RayTraceProcedural: 0 - m_RayTracingAccelStructBuildFlagsOverride: 0 - m_RayTracingAccelStructBuildFlags: 1 - m_SmallMeshCulling: 1 - m_RenderingLayerMask: 1 - m_RendererPriority: 0 - m_Materials: - - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} - m_StaticBatchInfo: - firstSubMesh: 0 - subMeshCount: 0 - m_StaticBatchRoot: {fileID: 0} - m_ProbeAnchor: {fileID: 0} - m_LightProbeVolumeOverride: {fileID: 0} - m_ScaleInLightmap: 1 - m_ReceiveGI: 1 - m_PreserveUVs: 0 - m_IgnoreNormalsForChartDetection: 0 - m_ImportantGI: 0 - m_StitchLightmapSeams: 1 - m_SelectedEditorRenderState: 3 - m_MinimumChartSize: 4 - m_AutoUVMaxDistance: 0.5 - m_AutoUVMaxAngle: 89 - m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 - m_SortingOrder: 0 - m_AdditionalVertexStreams: {fileID: 0} ---- !u!33 &1206022456 -MeshFilter: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1206022453} - m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} ---- !u!4 &1206022457 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1206022453} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 10, y: 10, z: 10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &1211923374 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1211923376} - - component: {fileID: 1211923375} - - component: {fileID: 1211923378} - - component: {fileID: 1211923377} - m_Layer: 0 - m_Name: '[NetworkManager] (Multiprocess)' - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &1211923375 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1211923374} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} - m_Name: - m_EditorClassIdentifier: - NetworkManagerExpanded: 0 - NetworkConfig: - ProtocolVersion: 0 - NetworkTransport: {fileID: 2027640073} - PlayerPrefab: {fileID: 4700706668509470175, guid: 7eeaaf9e50c0afc4dab93584a54fb0d6, - type: 3} - Prefabs: - NetworkPrefabsLists: [] - TickRate: 30 - ClientConnectionBufferTimeout: 10 - ConnectionApproval: 0 - ConnectionData: - EnableTimeResync: 0 - TimeResyncInterval: 30 - EnsureNetworkVariableLengthSafety: 0 - EnableSceneManagement: 1 - ForceSamePrefabs: 1 - RecycleNetworkIds: 1 - NetworkIdRecycleDelay: 120 - RpcHashSize: 0 - LoadSceneTimeOut: 120 - SpawnTimeout: 10 - EnableNetworkLogs: 1 - NetworkTopology: 0 - UseCMBService: 0 - AutoSpawnPlayerPrefabClientSide: 1 - NetworkProfilingMetrics: 1 - OldPrefabList: - - Override: 0 - Prefab: {fileID: 9115731988109684252, guid: c2851feb7276442cc86a6f2d1d69ea11, - type: 3} - SourcePrefabToOverride: {fileID: 0} - SourceHashToOverride: 0 - OverridingTargetPrefab: {fileID: 0} - RunInBackground: 1 - LogLevel: 1 ---- !u!4 &1211923376 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1211923374} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: - - {fileID: 2027640072} - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &1211923377 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1211923374} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 068bf11ceb1344667af4cc40950f44f4, type: 3} - m_Name: - m_EditorClassIdentifier: - ReferencedPrefab: {fileID: 9115731988109684252, guid: c2851feb7276442cc86a6f2d1d69ea11, - type: 3} ---- !u!114 &1211923378 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1211923374} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 55d1c75ce242745ac98f7e7aca6d2d19, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!1 &1274245423 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1274245425} - - component: {fileID: 1274245424} - - component: {fileID: 1274245426} - m_Layer: 0 - m_Name: TestCoordinator - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &1274245424 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1274245423} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} - m_Name: - m_EditorClassIdentifier: - GlobalObjectIdHash: 2217825759 - InScenePlacedSourceGlobalObjectIdHash: 0 - DeferredDespawnTick: 0 - Ownership: 1 - AlwaysReplicateAsRoot: 0 - SynchronizeTransform: 1 - ActiveSceneSynchronization: 0 - SceneMigrationSynchronization: 1 - SpawnWithObservers: 1 - DontDestroyWithOwner: 0 - AutoObjectParentSync: 1 - SyncOwnerTransformWhenParented: 1 - AllowOwnerToParent: 0 ---- !u!4 &1274245425 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1274245423} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 10, y: 10, z: 10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &1274245426 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1274245423} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ef1240e0784f84eadb77fe822e2e03c7, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!1 &2027640071 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 2027640072} - - component: {fileID: 2027640073} - m_Layer: 0 - m_Name: UTP - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!4 &2027640072 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2027640071} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 1211923376} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &2027640073 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2027640071} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3} - m_Name: - m_EditorClassIdentifier: - m_ProtocolType: 0 - m_UseWebSockets: 0 - m_UseEncryption: 0 - m_MaxPacketQueueSize: 128 - m_MaxPayloadSize: 4096 - m_HeartbeatTimeoutMS: 500 - m_ConnectTimeoutMS: 1000 - m_MaxConnectAttempts: 60 - m_DisconnectTimeoutMS: 30000 - ConnectionData: - Address: 127.0.0.1 - Port: 7777 - ServerListenAddress: 127.0.0.1 - DebugSimulator: - PacketDelayMS: 0 - PacketJitterMS: 0 - PacketDropRate: 0 ---- !u!1660057539 &9223372036854775807 -SceneRoots: - m_ObjectHideFlags: 0 - m_Roots: - - {fileID: 941021724} - - {fileID: 127222502} - - {fileID: 1211923376} - - {fileID: 1206022457} - - {fileID: 996484661} - - {fileID: 160940368} - - {fileID: 1274245425} - - {fileID: 430011407} diff --git a/testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs b/testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs new file mode 100644 index 0000000000..23b1b909a8 --- /dev/null +++ b/testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Unity.Netcode; +using UnityEngine; +using UnityEngine.SceneManagement; + +#if UNITY_6000_6_OR_NEWER && UNITY_EDITOR + [Unity.Scripting.LifecycleManagement.AutoStaticsCleanup] +#endif +public partial class MoveDynamicSpawnInAwake : MonoBehaviour +{ + public NetworkObject MovedObject { get; private set; } + private static readonly HashSet k_AlreadyMovedObjects = new(); + private void Awake() + { + var networkManager = NetworkManager.Singleton; + if (networkManager == null || !networkManager.IsListening) + { + return; + } + + foreach (var spawnedObject in networkManager.SpawnManager.SpawnedObjects.Values) + { + if (spawnedObject == null || !spawnedObject.IsSpawned || !spawnedObject.HasAuthority || spawnedObject.IsPlayerObject || spawnedObject.InScenePlaced) + { + continue; + } + + if (!k_AlreadyMovedObjects.Add(spawnedObject)) + { + continue; + } + + MovedObject = spawnedObject; + SceneManager.MoveGameObjectToScene(spawnedObject.gameObject, gameObject.scene); + return; + } + } +} diff --git a/testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs.meta b/testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs.meta new file mode 100644 index 0000000000..053fb00eb0 --- /dev/null +++ b/testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 566f22ce0b192499d8728df60af83423 \ No newline at end of file diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/EmptyScene3.unity.meta b/testproject/Assets/Tests/Manual/IntegrationTestScenes/EmptyScene3.unity.meta index 46669dd13d..88bb22b242 100644 --- a/testproject/Assets/Tests/Manual/IntegrationTestScenes/EmptyScene3.unity.meta +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/EmptyScene3.unity.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 -guid: abd4c8b51c445d54faa16c67ac973f1b +guid: bfb4addb64cf6455a88668eba2f759ba DefaultImporter: externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity new file mode 100644 index 0000000000..1cfda7fc2d --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity @@ -0,0 +1,198 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &1633685737 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1633685739} + - component: {fileID: 1633685738} + - component: {fileID: 1633685740} + m_Layer: 0 + m_Name: InSceneObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1633685738 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1633685737} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 974939462 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + m_InScenePlaced: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 0 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 +--- !u!4 &1633685739 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1633685737} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1633685740 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1633685737} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 112072ebb4fab6341bfee4bd9d7e58da, type: 3} + m_Name: + m_EditorClassIdentifier: TestProject.ManualTests::TestProject.ManualTests.MoveInScenePlacedToDDOL + ShowTopMostFoldoutHeaderGroup: 1 +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1633685739} diff --git a/testproject/Assets/Scenes/MultiprocessTestScene.unity.meta b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity.meta similarity index 74% rename from testproject/Assets/Scenes/MultiprocessTestScene.unity.meta rename to testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity.meta index 5a9d45d780..dfa51863fd 100644 --- a/testproject/Assets/Scenes/MultiprocessTestScene.unity.meta +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 76743cb7b342c49279327834918a9c6e +guid: 4c20c17b4f92e634f8796b0460851d49 DefaultImporter: externalObjects: {} userData: diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity b/testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity new file mode 100644 index 0000000000..f5b385433f --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity @@ -0,0 +1,170 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &1786247463 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1786247465} + - component: {fileID: 1786247464} + m_Layer: 0 + m_Name: MoveDynamicSpawnInAwake + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1786247464 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1786247463} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 566f22ce0b192499d8728df60af83423, type: 3} + m_Name: + m_EditorClassIdentifier: TestProject::MoveDynamicSpawnInAwake +--- !u!4 &1786247465 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1786247463} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.09747, y: 0, z: 1.59747} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1786247465} diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity.meta b/testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity.meta new file mode 100644 index 0000000000..46669dd13d --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: abd4c8b51c445d54faa16c67ac973f1b +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs b/testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs new file mode 100644 index 0000000000..0e9fa457a9 --- /dev/null +++ b/testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs @@ -0,0 +1,34 @@ +using Unity.Netcode; +using UnityEngine; + +namespace TestProject.ManualTests +{ + public class MoveInScenePlacedToDDOL : NetworkBehaviour + { + public bool ProcessedRpc { get; private set; } + + private void Awake() + { + ProcessedRpc = false; + var networkObject = GetComponent(); + Debug.Log($"[{name}][Moving to DDOL] InScenePlaced: {networkObject.InScenePlaced}"); + DontDestroyOnLoad(gameObject); + } + + protected override void OnNetworkPostSpawn() + { + if (HasAuthority) + { + SendOnSpawnRpc(); + } + + base.OnNetworkPostSpawn(); + } + + [Rpc(SendTo.Everyone)] + private void SendOnSpawnRpc(RpcParams rpcParams = default) + { + ProcessedRpc = true; + } + } +} diff --git a/testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs.meta b/testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs.meta new file mode 100644 index 0000000000..739e4ca2d4 --- /dev/null +++ b/testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 112072ebb4fab6341bfee4bd9d7e58da \ No newline at end of file diff --git a/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPool.cs b/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPool.cs index 7821d2af27..9f441b8f58 100644 --- a/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPool.cs +++ b/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPool.cs @@ -496,8 +496,8 @@ public void UpdateSpawnsPerSecond() { StartSpawningBoxes(); } - else //Handle case where spawning coroutine is running but we set our spawn rate to zero - if (SpawnsPerSecond == 0 && m_IsSpawningObjects) + //Handle case where spawning coroutine is running but we set our spawn rate to zero + else if (SpawnsPerSecond == 0 && m_IsSpawningObjects) { m_IsSpawningObjects = false; StopCoroutine(SpawnObjects()); diff --git a/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPoolAdditive.cs b/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPoolAdditive.cs index f714d937de..44decc3dce 100644 --- a/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPoolAdditive.cs +++ b/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPoolAdditive.cs @@ -342,8 +342,8 @@ public void UpdateSpawnsPerSecond() { StartSpawningBoxes(); } - else //Handle case where spawning coroutine is running but we set our spawn rate to zero - if (SpawnsPerSecond == 0 && m_IsSpawningObjects) + //Handle case where spawning coroutine is running but we set our spawn rate to zero + else if (SpawnsPerSecond == 0 && m_IsSpawningObjects) { m_IsSpawningObjects = false; InternalStopCoroutine(); diff --git a/testproject/Assets/Tests/Runtime/AddressablesTests.cs b/testproject/Assets/Tests/Runtime/AddressablesTests.cs index fb492b9540..d81837ecea 100644 --- a/testproject/Assets/Tests/Runtime/AddressablesTests.cs +++ b/testproject/Assets/Tests/Runtime/AddressablesTests.cs @@ -7,6 +7,8 @@ using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.AsyncOperations; +using UnityEngine.SceneManagement; using UnityEngine.TestTools; using Assert = UnityEngine.Assertions.Assert; @@ -28,6 +30,7 @@ public class AddressablesTests : NetcodeIntegrationTest protected override bool m_TearDownIsACoroutine => false; private const string k_ValidObject = "AddressableTestObject.prefab"; + private const string k_ValidScene = "Assets/Scenes/AddressableInSceneObject.unity"; public AddressablesTests(HostOrServer hostOrServer) { @@ -60,6 +63,24 @@ private IEnumerator LoadAsset(AssetReferenceGameObject asset, NetcodeIntegration prefab.Result = handle.Result; } + private IEnumerator LoadSceneWithInSceneObject(AssetReference asset, NetcodeIntegrationTestHelpers.ResultWrapper prefab) + { + var handle = Addressables.LoadSceneAsync(asset, LoadSceneMode.Additive); + while (!handle.IsDone) + { + var nextFrameNumber = Time.frameCount + 1; + yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); + } + + Assert.AreEqual(AsyncOperationStatus.Succeeded, handle.Status, "Addressables.LoadSceneAsync failed!"); + + foreach (var networkObject in FindObjects.FromSceneByType(handle.Result.Scene, false)) + { + prefab.Result = networkObject.gameObject; + break; + } + } + protected void StartWithAddressableAssetAdded() { StartServerAndClientsWithTimeTravel(); @@ -74,7 +95,7 @@ private void AddPrefab(GameObject prefab) } } - private void SpawnAndValidate(GameObject prefab, bool waitAndAddOnClient = false) + private void SpawnAndValidate(GameObject prefab, bool waitAndAddOnClient = false, bool wasLoadedFromScene = false) { // Have to spawn it ourselves. var serverObj = Object.Instantiate(prefab); @@ -84,7 +105,14 @@ private void SpawnAndValidate(GameObject prefab, bool waitAndAddOnClient = false // Prefabs loaded by addressables actually don't show up in this search. // Unlike other tests that make prefabs programmatically, those aren't added to the scene until they're instantiated - Assert.AreEqual(1, objs.Length); + var numExpected = 1; + if (wasLoadedFromScene) + { + // If prefab was loaded from the scene, there'll be an additional object found + numExpected++; + } + + Assert.AreEqual(numExpected, objs.Length); var startTime = MockTimeProvider.StaticRealTimeSinceStartup; @@ -95,7 +123,7 @@ private void SpawnAndValidate(GameObject prefab, bool waitAndAddOnClient = false // Since it's not added, after the CreateObjectMessage is received, it's not spawned yet // Verify that to be the case as a precondition. objs = FindObjects.ByType(); - Assert.AreEqual(1, objs.Length); + Assert.AreEqual(numExpected, objs.Length); WaitForConditionOrTimeOutWithTimeTravel(() => MockTimeProvider.StaticRealTimeSinceStartup - startTime >= m_ClientNetworkManagers[0].NetworkConfig.SpawnTimeout - 0.25); foreach (var client in m_ClientNetworkManagers) { @@ -104,12 +132,22 @@ private void SpawnAndValidate(GameObject prefab, bool waitAndAddOnClient = false } objs = FindObjects.ByType(); - Assert.AreEqual(NumberOfClients + 1, objs.Length); + Assert.AreEqual(NumberOfClients + numExpected, objs.Length); foreach (var obj in objs) { Assert.AreEqual(1234567, obj.AnIntVal); Assert.AreEqual("1234567", obj.AStringVal); Assert.AreEqual("12345671234567", obj.GetValue()); + + // TODO-[MTT-15388]: Object spawned from a scene should be InScenePlaced after this ticket + if (obj.IsSpawned) + { + Assert.IsFalse(obj.NetworkObject.InScenePlaced, "Object was dynamically spawned and should be marked as such!"); + } + else + { + Assert.IsTrue(obj.NetworkObject.InScenePlaced, "Object that was loaded from scene should have been marked as in-scene placed during loading!"); + } } } @@ -148,7 +186,7 @@ public IEnumerator WhenLoadingAValidObjectAfterStarting_SpawningItSucceedsOnServ } [UnityTest] - public IEnumerator WhenSpawningServerPrefabBeforeClientPrefabHasLoaded_SpawningItSucceedsOnServerAndClientAfterConfiguredDelay([Values(1, 2, 3)] int timeout) + public IEnumerator WhenSpawningServerPrefabBeforeClientPrefabHasLoaded_SpawningItSucceedsOnServerAndClientAfterDelay() { var asset = new AssetReferenceGameObject(k_ValidObject); @@ -156,7 +194,7 @@ public IEnumerator WhenSpawningServerPrefabBeforeClientPrefabHasLoaded_SpawningI m_ServerNetworkManager.NetworkConfig.ForceSamePrefabs = false; foreach (var client in m_ClientNetworkManagers) { - client.NetworkConfig.SpawnTimeout = timeout; + client.NetworkConfig.SpawnTimeout = 3; client.NetworkConfig.ForceSamePrefabs = false; } @@ -167,6 +205,32 @@ public IEnumerator WhenSpawningServerPrefabBeforeClientPrefabHasLoaded_SpawningI SpawnAndValidate(prefabResult.Result, true); } + + // TODO-[MTT-15388]: Reconsider whether this test should be valid + // Reported on Github issue https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues/4049 + //[UnityTest] + //public IEnumerator RegisteringPrefabFromLoadedAddressablesSceneWorks() + //{ + // var asset = new AssetReference(k_ValidScene); + + // CreateServerAndClients(); + // foreach (var manager in m_NetworkManagers) + // { + // manager.NetworkConfig.ForceSamePrefabs = false; + // } + + // StartServerAndClientsWithTimeTravel(); + + // var prefabResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); + // yield return LoadSceneWithInSceneObject(asset, prefabResult); + + // foreach (var manager in m_NetworkManagers) + // { + // manager.AddNetworkPrefab(prefabResult.Result); + // } + + // SpawnAndValidate(prefabResult.Result, wasLoadedFromScene: true); + //} } } #endif diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index 474c9dabbe..d685fb68ca 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -3,9 +3,11 @@ using System.Linq; using NUnit.Framework; using Unity.Netcode; +using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; +using static Unity.Netcode.Components.NetworkAnimator; namespace TestProject.RuntimeTests @@ -336,7 +338,40 @@ public void ParameterExcludedTests() VerboseDebug($" ------------------ Parameter Test [{m_OwnerShipMode}] Stopping ------------------ "); } + private unsafe void MockWritingParameters(ref FastBufferWriter writer) + { + writer.Seek(0); + writer.Truncate(); + + // Write out how many parameter entries to read + BytePacker.WriteValuePacked(writer, (uint)1); + // Write an invalid index level (anything would be invalid with no parameters) + BytePacker.WriteValuePacked(writer, (uint)1000); + // Write some value for the invalid parameter + BytePacker.WriteValuePacked(writer, (uint)10); + } + + [Test] + public void ParameterBoundsCheck() + { + var gameObject = new GameObject(); + gameObject.AddComponent(); + var networkAnimator = gameObject.AddComponent(); + + var writer = new FastBufferWriter(40, Unity.Collections.Allocator.TempJob); + // Mock a parameter update with an invalid index + MockWritingParameters(ref writer); + + var invalidParameters = new ParametersUpdateMessage() + { + Parameters = writer.ToArray() + }; + // Expect an error message + LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex($"parameters. Ignoring the remainger of this {nameof(ParametersUpdateMessage)}!")); + // Pass in the invalid ParametersUpdateMessage + networkAnimator.UpdateParameters(ref invalidParameters); + } private bool AllTriggersDetected(OwnerShipMode ownerShipMode) { @@ -527,8 +562,7 @@ private bool AllObserversSameLayerWeight(OwnerShipMode ownerShipMode, int layer, return false; } } - else - if (animatorTestHelper.Value.GetLayerWeight(layer) != targetWeight) + else if (animatorTestHelper.Value.GetLayerWeight(layer) != targetWeight) { return false; } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs new file mode 100644 index 0000000000..807ff09eeb --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs @@ -0,0 +1,75 @@ +using System.Collections; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; + +namespace TestProject.RuntimeTests +{ + internal class InScenePlacedProcessorTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 0; + + private static readonly string k_DynamicObjectMoverScene = "MoveADynamicObjectInAwake"; + + private GameObject m_DynamicSpawnPrefab; + + protected override void OnServerAndClientsCreated() + { + m_DynamicSpawnPrefab = CreateNetworkObjectPrefab("DynamicSpawnObject"); + base.OnServerAndClientsCreated(); + } + + [UnityTest] + public IEnumerator InScenePlacedProcessorSkipsMovedObject() + { + var authority = GetAuthorityNetworkManager(); + var spawnedObj = SpawnObject(m_DynamicSpawnPrefab, authority).GetComponent(); + + yield return WaitForSpawnedOnAllOrTimeOut(spawnedObj); + AssertOnTimeout("Timed out waiting for object to spawn!"); + + Assert.IsFalse(spawnedObj.InScenePlaced, "Dynamically spawned object should not be InScenePlaced!"); + + authority.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + var status = authority.SceneManager.LoadScene(k_DynamicObjectMoverScene, LoadSceneMode.Additive); + Assert.True(status == SceneEventProgressStatus.Started, $"Failed to start lading scene {k_DynamicObjectMoverScene} with status {status}!"); + yield return WaitForConditionOrTimeOut(() => m_SceneLoaded.IsValid()); + AssertOnTimeout("Timed out waiting for scene to load!"); + + var movers = FindObjects.ByType(); + foreach (var mover in movers) + { + var movedObject = mover.MovedObject; + Assert.IsFalse(movedObject == null); + Assert.IsFalse(movedObject.InScenePlaced, "Dynamically spawned object should not be re-processed as InScenePlaced!"); + Assert.AreEqual(mover.gameObject.scene, movedObject.gameObject.scene, "Object should have moved scenes!"); + } + + yield return CreateAndStartNewClient(); + AssertOnTimeout("Timed out waiting for late joining client!"); + + yield return WaitForSpawnedOnAllOrTimeOut(spawnedObj); + AssertOnTimeout("Timed out waiting for object to be spawned on late joining client!"); + } + + private Scene m_SceneLoaded; + private void SceneManager_OnSceneEvent(SceneEvent sceneEvent) + { + var authority = GetAuthorityNetworkManager(); + switch (sceneEvent.SceneEventType) + { + case SceneEventType.LoadComplete: + { + if (sceneEvent.ClientId == authority.LocalClientId) + { + m_SceneLoaded = sceneEvent.Scene; + } + return; + } + } + } + } +} diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs.meta new file mode 100644 index 0000000000..3d0a9439ec --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9abce9656f174f768488bb052afa8d92 +timeCreated: 1783723861 \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectSceneMigrationTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectSceneMigrationTests.cs index d5eb5f427c..736de24acf 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectSceneMigrationTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectSceneMigrationTests.cs @@ -1,6 +1,9 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; using NUnit.Framework; using Unity.Netcode; using Unity.Netcode.TestHelpers.Runtime; @@ -13,27 +16,30 @@ namespace TestProject.RuntimeTests { /// /// NetworkObject Scene Migration Integration Tests - /// - /// /// - public class NetworkObjectSceneMigrationTests : NetcodeIntegrationTest + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.DAHost)] + internal class NetworkObjectSceneMigrationTests : NetcodeIntegrationTest { - private List m_TestScenes = new List() { "EmptyScene1", "EmptyScene2", "EmptyScene3" }; + private readonly List m_TestScenes = new() { "EmptyScene1", "EmptyScene2", "EmptyScene3" }; protected override int NumberOfClients => 2; + private GameObject m_TestPrefab; private GameObject m_TestPrefabAutoSynchActiveScene; private GameObject m_TestPrefabDestroyWithScene; private Scene m_OriginalActiveScene; - private bool m_ClientsLoadedScene; - private bool m_ClientsUnloadedScene; - private Scene m_SceneLoaded; private List m_ServerSpawnedPrefabInstances = new List(); private List m_ServerSpawnedDestroyWithSceneInstances = new List(); - private List m_ScenesLoaded = new List(); - private string m_CurrentSceneLoading; - private string m_CurrentSceneUnloading; + private readonly List m_ScenesLoaded = new List(); + public NetworkObjectSceneMigrationTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + // TODO: [MTT-15430] Fix automatic scene object migration between clients + protected override bool UseCMBService() + { + return false; + } protected override IEnumerator OnSetup() { @@ -41,106 +47,70 @@ protected override IEnumerator OnSetup() return base.OnSetup(); } + protected override void OnCreatePlayerPrefab() + { + Object.DontDestroyOnLoad(m_PlayerPrefab); + m_PlayerPrefab.GetComponent().ActiveSceneSynchronization = true; + base.OnCreatePlayerPrefab(); + } + protected override void OnServerAndClientsCreated() { // Synchronize Scene Changes (default) Test Network Prefab m_TestPrefab = CreateNetworkObjectPrefab("TestObject"); + m_TestPrefab.AddComponent(); // Auto Synchronize Active Scene Changes Test Network Prefab - m_TestPrefabAutoSynchActiveScene = CreateNetworkObjectPrefab("ASASObject"); + m_TestPrefabAutoSynchActiveScene = CreateNetworkObjectPrefab("ActiveSceneSynchronizationObject"); m_TestPrefabAutoSynchActiveScene.GetComponent().ActiveSceneSynchronization = true; + m_TestPrefabAutoSynchActiveScene.AddComponent(); // Destroy With Scene Test Network Prefab - m_TestPrefabDestroyWithScene = CreateNetworkObjectPrefab("DWSObject"); + m_TestPrefabDestroyWithScene = CreateNetworkObjectPrefab("DestroyWithSceneObject"); m_TestPrefabDestroyWithScene.AddComponent(); + m_TestPrefabDestroyWithScene.AddComponent(); - DestroyWithSceneInstancesTestHelper.ShouldNeverSpawn = m_TestPrefabDestroyWithScene; + var neverSpawnObj = CreateNetworkObjectPrefab("ShouldNeverSpawn"); + var shouldNeverSpawn = neverSpawnObj.AddComponent(); + DestroyWithSceneInstancesTestHelper.ShouldNeverSpawn = shouldNeverSpawn; + + var authority = GetAuthorityNetworkManager(); + authority.OnServerStarted += OnServerStarted; base.OnServerAndClientsCreated(); } - private bool DidClientsSpawnInstance(NetworkObject serverObject, bool checkDestroyWithScene = false) - { - foreach (var networkManager in m_ClientNetworkManagers) - { - if (!s_GlobalNetworkObjects.ContainsKey(networkManager.LocalClientId)) - { - return false; - } - var clientNetworkObjects = s_GlobalNetworkObjects[networkManager.LocalClientId]; - if (!clientNetworkObjects.ContainsKey(serverObject.NetworkObjectId)) - { - return false; - } - - if (checkDestroyWithScene) - { - if (serverObject.DestroyWithScene != clientNetworkObjects[serverObject.NetworkObjectId]) - { - return false; - } - } - } - return true; - } - private bool VerifyAllClientsSpawnedInstances() + private void OnServerStarted() { - foreach (var serverObject in m_ServerSpawnedPrefabInstances) - { - if (!DidClientsSpawnInstance(serverObject)) - { - return false; - } - } - - foreach (var serverObject in m_ServerSpawnedDestroyWithSceneInstances) - { - if (!DidClientsSpawnInstance(serverObject, true)) - { - return false; - } - } - - return true; + var authority = GetAuthorityNetworkManager(); + authority.OnServerStarted -= OnServerStarted; + authority.SceneManager.ActiveSceneSynchronizationEnabled = true; } - private bool AreClientInstancesInTheRightScene(NetworkObject serverObject) + private bool VerifyAllScenesMatch(StringBuilder errorLog, List authorityInstances) { - foreach (var networkManager in m_ClientNetworkManagers) + foreach (var authorityInstance in authorityInstances) { - var clientNetworkObjects = s_GlobalNetworkObjects[networkManager.LocalClientId]; - if (clientNetworkObjects == null) - { - continue; - } - // If a networkObject is null then it was destroyed - if (clientNetworkObjects[serverObject.NetworkObjectId].gameObject.scene.name != serverObject.gameObject.scene.name) + foreach (var networkManager in m_NetworkManagers) { - return false; - } - } - return true; - } + if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityInstance.NetworkObjectId, out var instance)) + { + errorLog.AppendLine($"[{authorityInstance.name}] Client-{networkManager.LocalClientId} doesn't have a local version of network object {authorityInstance.name} with id {authorityInstance.NetworkObjectId}"); + return false; + } - private bool VerifySpawnedObjectsMigrated() - { - foreach (var serverObject in m_ServerSpawnedPrefabInstances) - { - if (!AreClientInstancesInTheRightScene(serverObject)) - { - return false; - } - } + if (instance.gameObject.scene.name != authorityInstance.gameObject.scene.name) + { + errorLog.AppendLine($"[{instance.name}] NetworkObject-{authorityInstance.NetworkObjectId} is in the wrong scene! Expected: {authorityInstance.gameObject.scene.name}, Actual: {instance.gameObject.scene.name}"); + return false; + } - foreach (var serverObject in m_ServerSpawnedDestroyWithSceneInstances) - { - if (!AreClientInstancesInTheRightScene(serverObject)) - { - return false; + // The SceneOrigin should never change + var originalSceneTracker = instance.GetComponent(); + Assert.AreEqual(originalSceneTracker.SceneWhereAwakeHappened, (NetworkSceneHandle)instance.SceneOrigin.handle, "The SceneOrigin of an object should never change!"); } } - return true; } @@ -154,28 +124,31 @@ private bool VerifySpawnedObjectsMigrated() [UnityTest] public IEnumerator MigrateIntoNewSceneTest() { + var authority = GetAuthorityNetworkManager(); + + var authoritySpawnedInstances = new List(); // Spawn 9 NetworkObject instances for (int i = 0; i < k_MaxObjectsToSpawn; i++) { - var serverInstance = Object.Instantiate(m_TestPrefab); - var serverNetworkObject = serverInstance.GetComponent(); - serverNetworkObject.Spawn(); - m_ServerSpawnedPrefabInstances.Add(serverNetworkObject); + var instance = SpawnObject(m_TestPrefab, authority); + var spawnedObject = instance.GetComponent(); + authoritySpawnedInstances.Add(spawnedObject); } - yield return WaitForConditionOrTimeOut(VerifyAllClientsSpawnedInstances); + + yield return WaitForSpawnedOnAllOrTimeOut(authoritySpawnedInstances); AssertOnTimeout($"Timed out waiting for all clients to spawn {nameof(NetworkObject)}s!"); // Now load three scenes to migrate the newly spawned NetworkObjects into - m_ServerNetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; - for (int i = 0; i < 3; i++) + authority.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + foreach (var sceneToLoad in m_TestScenes) { - m_ClientsLoadedScene = false; - m_CurrentSceneLoading = m_TestScenes[i]; - var status = m_ServerNetworkManager.SceneManager.LoadScene(m_TestScenes[i], LoadSceneMode.Additive); - Assert.True(status == SceneEventProgressStatus.Started, $"Failed to start loading scene {m_CurrentSceneLoading}! Return status: {status}"); - yield return WaitForConditionOrTimeOut(() => m_ClientsLoadedScene); - AssertOnTimeout($"Timed out waiting for all clients to load scene {m_CurrentSceneLoading}!"); + yield return LoadScene(authority, sceneToLoad); } + authority.SceneManager.OnSceneEvent -= SceneManager_OnSceneEvent; + Assert.AreEqual(m_TestScenes.Count, m_ScenesLoaded.Count, "Not all the test scenes were loaded!"); + + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); + AssertOnTimeout("[After spawn] Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); var objectCount = 0; // Migrate each networkObject into one of the three scenes. @@ -183,31 +156,32 @@ public IEnumerator MigrateIntoNewSceneTest() foreach (var scene in m_ScenesLoaded) { // Now migrate the NetworkObject - SceneManager.MoveGameObjectToScene(m_ServerSpawnedPrefabInstances[objectCount].gameObject, scene); - SceneManager.MoveGameObjectToScene(m_ServerSpawnedPrefabInstances[objectCount + 1].gameObject, scene); - SceneManager.MoveGameObjectToScene(m_ServerSpawnedPrefabInstances[objectCount + 2].gameObject, scene); + SceneManager.MoveGameObjectToScene(authoritySpawnedInstances[objectCount].gameObject, scene); + SceneManager.MoveGameObjectToScene(authoritySpawnedInstances[objectCount + 1].gameObject, scene); + SceneManager.MoveGameObjectToScene(authoritySpawnedInstances[objectCount + 2].gameObject, scene); objectCount += 3; } - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // Register for the server-side client synchronization so we can send an object scene migration event at the same time // the new client begins to synchronize - m_ServerNetworkManager.SceneManager.OnSynchronize += MigrateObjects_OnSynchronize; + m_ServerSpawnedPrefabInstances = authoritySpawnedInstances; + authority.SceneManager.OnSynchronize += MigrateObjects_OnSynchronize; // Verify that a late joining client synchronizes properly even while new scene migrations occur // during its synchronization yield return CreateAndStartNewClient(); - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"[Late Joined Client] Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // Verify that a late joining client synchronizes properly even if we migrate // during its synchronization and despawn some of the NetworkObjects migrated. - m_ServerNetworkManager.SceneManager.OnSynchronize += MigrateAndDespawnObjects_OnSynchronize; + authority.SceneManager.OnSynchronize += MigrateAndDespawnObjects_OnSynchronize; yield return CreateAndStartNewClient(); - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"[Late Joined Client] Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); } @@ -219,19 +193,27 @@ public IEnumerator MigrateIntoNewSceneTest() /// private void MigrateAndDespawnObjects_OnSynchronize(ulong clientId) { - var objectCount = 0; + var authority = GetAuthorityNetworkManager(); // Migrate the NetworkObjects into different scenes than they originally were migrated into for (int i = m_ServerSpawnedPrefabInstances.Count - 1; i >= 0; i--) { var scene = m_ScenesLoaded[i % m_ScenesLoaded.Count]; - SceneManager.MoveGameObjectToScene(m_ServerSpawnedPrefabInstances[i].gameObject, scene); + var obj = m_ServerSpawnedPrefabInstances[i]; + if (m_DistributedAuthority) + { + // When the new client joins, authority will be distributed. + // Ensure we have the authority instance. + obj = GetAuthorityInstance(obj); + } + SceneManager.MoveGameObjectToScene(obj.gameObject, scene); // De-spawn every-other object if (i % 2 == 0) { - m_ServerSpawnedPrefabInstances[objectCount + i].Despawn(); + obj.Despawn(); m_ServerSpawnedPrefabInstances.RemoveAt(i); } } + authority.SceneManager.OnSynchronize -= MigrateObjects_OnSynchronize; } /// @@ -252,7 +234,24 @@ private void MigrateObjects_OnSynchronize(ulong clientId) } // Unsubscribe to this event for this part of the test - m_ServerNetworkManager.SceneManager.OnSynchronize -= MigrateObjects_OnSynchronize; + GetAuthorityNetworkManager().SceneManager.OnSynchronize -= MigrateObjects_OnSynchronize; + } + + protected override void OnNewClientCreated(NetworkManager networkManager) + { + var authority = GetAuthorityNetworkManager(); + foreach (var prefab in authority.NetworkConfig.Prefabs.Prefabs) + { + networkManager.NetworkConfig.Prefabs.Add(prefab); + } + networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; + base.OnNewClientCreated(networkManager); + } + + private void SetActiveScene(Scene scene) + { + Debug.Log($"[Previous = {SceneManager.GetActiveScene().name}][New = {scene.name}] Changing the active scene!"); + SceneManager.SetActiveScene(scene); } /// @@ -263,36 +262,47 @@ private void MigrateObjects_OnSynchronize(ulong clientId) [UnityTest] public IEnumerator ActiveSceneSynchronizationTest() { + var authority = GetAuthorityNetworkManager(); // Disable resynchronization for this test to avoid issues with trying // to synchronize them. NetworkSceneManager.DisableReSynchronization = true; + // Load three scenes first + authority.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + foreach (var sceneName in m_TestScenes) + { + yield return LoadScene(authority, sceneName); + } + authority.SceneManager.OnSceneEvent -= SceneManager_OnSceneEvent; + + // Set the active scene to be the 1st scene loaded so we don't instantiate within the test runner scene. + SetActiveScene(m_ScenesLoaded[0]); + + var autoSyncActive = new List(); // Spawn 3 NetworkObject instances that auto synchronize to active scene changes for (int i = 0; i < 3; i++) { - var serverInstance = Object.Instantiate(m_TestPrefabAutoSynchActiveScene); - var serverNetworkObject = serverInstance.GetComponent(); // We are also testing that objects marked to synchronize with changes to // the active scene and marked to destroy with scene =are destroyed= if // the scene being unloaded is currently the active scene and the scene that // the NetworkObjects reside within. - serverNetworkObject.Spawn(true); - m_ServerSpawnedPrefabInstances.Add(serverNetworkObject); + var serverInstance = SpawnObject(m_TestPrefabAutoSynchActiveScene, authority, true); + var serverNetworkObject = serverInstance.GetComponent(); + autoSyncActive.Add(serverNetworkObject); } - // Spawn 3 NetworkObject instances that do not auto synchronize to active scene changes // and ==should not be== destroyed with the scene (these should be the only remaining // instances) + var autoSyncInactive = new List(); for (int i = 0; i < 3; i++) { - var serverInstance = Object.Instantiate(m_TestPrefab); - var serverNetworkObject = serverInstance.GetComponent(); // This set of NetworkObjects will be used to verify that NetworkObjets // spawned with DestroyWithScene set to false will migrate into the current // active scene if the scene they currently reside within is destroyed and // is not the currently active scene. - serverNetworkObject.Spawn(); - m_ServerSpawnedPrefabInstances.Add(serverNetworkObject); + var serverInstance = SpawnObject(m_TestPrefab, authority); + var serverNetworkObject = serverInstance.GetComponent(); + autoSyncInactive.Add(serverNetworkObject); } // Spawn 3 NetworkObject instances that do not auto synchronize to active scene changes @@ -307,104 +317,90 @@ public IEnumerator ActiveSceneSynchronizationTest() serverNetworkObject.Spawn(true); m_ServerSpawnedDestroyWithSceneInstances.Add(serverNetworkObject); } + var authoritySpawnedInstances = new List(); + authoritySpawnedInstances.AddRange(autoSyncActive); + authoritySpawnedInstances.AddRange(autoSyncInactive); + authoritySpawnedInstances.AddRange(m_ServerSpawnedDestroyWithSceneInstances); - yield return WaitForConditionOrTimeOut(VerifyAllClientsSpawnedInstances); + yield return WaitForSpawnedOnAllOrTimeOut(authoritySpawnedInstances); AssertOnTimeout($"Timed out waiting for all clients to spawn {nameof(NetworkObject)}s!"); - // Now load three scenes - m_ServerNetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; - for (int i = 0; i < 3; i++) - { - m_ClientsLoadedScene = false; - m_CurrentSceneLoading = m_TestScenes[i]; - var loadStatus = m_ServerNetworkManager.SceneManager.LoadScene(m_TestScenes[i], LoadSceneMode.Additive); - Assert.True(loadStatus == SceneEventProgressStatus.Started, $"Failed to start loading scene {m_CurrentSceneLoading}! Return status: {loadStatus}"); - yield return WaitForConditionOrTimeOut(() => m_ClientsLoadedScene); - AssertOnTimeout($"Timed out waiting for all clients to load scene {m_CurrentSceneLoading}!"); - } - + var sceneToMigrateTo = m_ScenesLoaded[2]; // Migrate the instances that don't synchronize with active scene changes into the 3rd loaded scene // (We are making sure these stay in the same scene they are migrated into) - for (int i = 3; i < m_ServerSpawnedPrefabInstances.Count; i++) + foreach (var spawnedObject in autoSyncInactive) { - SceneManager.MoveGameObjectToScene(m_ServerSpawnedPrefabInstances[i].gameObject, m_ScenesLoaded[2]); + SceneManager.MoveGameObjectToScene(spawnedObject.gameObject, sceneToMigrateTo); } // Migrate the instances that don't synchronize with active scene changes and are destroyed with the // scene unloading into the 3rd loaded scene // (We are making sure these get destroyed when the scene is unloaded) - for (int i = 0; i < m_ServerSpawnedDestroyWithSceneInstances.Count; i++) + foreach (var spawnedObject in m_ServerSpawnedDestroyWithSceneInstances) { - SceneManager.MoveGameObjectToScene(m_ServerSpawnedDestroyWithSceneInstances[i].gameObject, m_ScenesLoaded[2]); + SceneManager.MoveGameObjectToScene(spawnedObject.gameObject, sceneToMigrateTo); } // Make sure they migrated to the proper scene - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // Now change the active scene - SceneManager.SetActiveScene(m_ScenesLoaded[1]); - // We have to do this - Object.DontDestroyOnLoad(m_TestPrefabAutoSynchActiveScene); + var newActiveScene = m_ScenesLoaded[1]; + SetActiveScene(newActiveScene); // First, make sure server-side scenes and client side scenes match - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // Verify that the auto-active-scene synchronization NetworkObjects migrated to the newly // assigned active scene - for (int i = 0; i < 3; i++) + foreach (var obj in autoSyncActive) { - Assert.True(m_ServerSpawnedPrefabInstances[i].gameObject.scene == m_ScenesLoaded[1], - $"{m_ServerSpawnedPrefabInstances[i].gameObject.name} did not migrate into scene {m_ScenesLoaded[1].name}!"); + Assert.AreEqual(newActiveScene, obj.gameObject.scene, $"{obj.gameObject.name} did not migrate into scene {newActiveScene.name}!"); } // Verify that the other NetworkObjects that don't synchronize with active scene changes did // not migrate into the active scene. - for (int i = 3; i < m_ServerSpawnedPrefabInstances.Count; i++) + foreach (var obj in autoSyncInactive) { - Assert.False(m_ServerSpawnedPrefabInstances[i].gameObject.scene == m_ScenesLoaded[1], - $"{m_ServerSpawnedPrefabInstances[i].gameObject.name} migrated into scene {m_ScenesLoaded[1].name}!"); + Assert.AreNotEqual(newActiveScene, obj.gameObject.scene, $"{obj.gameObject.name} migrated into scene {newActiveScene.name}!"); } - for (int i = 0; i < 3; i++) + foreach (var obj in m_ServerSpawnedDestroyWithSceneInstances) { - Assert.False(m_ServerSpawnedDestroyWithSceneInstances[i].gameObject.scene == m_ScenesLoaded[1], - $"{m_ServerSpawnedDestroyWithSceneInstances[i].gameObject.name} migrated into scene {m_ScenesLoaded[1].name}!"); + Assert.AreNotEqual(newActiveScene, obj.gameObject.scene, $"{obj.gameObject.name} migrated into scene {newActiveScene.name}!"); } // Verify that a late joining client synchronizes properly and destroys the appropriate NetworkObjects yield return CreateAndStartNewClient(); - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + AssertOnTimeout("Failed to start or create a new client!"); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"[Late Joined Client #1] Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // Now, unload the scene containing the NetworkObjects that don't synchronize with active scene changes DestroyWithSceneInstancesTestHelper.NetworkObjectDestroyed += OnNonActiveSynchDestroyWithSceneNetworkObjectDestroyed; - m_ClientsUnloadedScene = false; - m_CurrentSceneUnloading = m_ScenesLoaded[2].name; - var status = m_ServerNetworkManager.SceneManager.UnloadScene(m_ScenesLoaded[2]); - Assert.True(status == SceneEventProgressStatus.Started, $"Failed to start unloading scene {m_ScenesLoaded[2].name} with status {status}!"); - yield return WaitForConditionOrTimeOut(() => m_ClientsUnloadedScene); + + yield return UnloadScene(authority, sceneToMigrateTo); // Clean up any destroyed NetworkObjects - for (int i = m_ServerSpawnedPrefabInstances.Count - 1; i >= 0; i--) + for (int i = authoritySpawnedInstances.Count - 1; i >= 0; i--) { - if (m_ServerSpawnedPrefabInstances[i] == null) + if (authoritySpawnedInstances[i] == null) { - m_ServerSpawnedPrefabInstances.RemoveAt(i); + authoritySpawnedInstances.RemoveAt(i); } } - AssertOnTimeout($"Timed out waiting for all clients to unload scene {m_CurrentSceneUnloading}!"); - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + AssertOnTimeout($"Timed out waiting for all clients to unload scene {sceneToMigrateTo.name}!"); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // Verify that the NetworkObjects that don't synchronize with active scene changes but marked to not // destroy with the scene are migrated into the current active scene - for (int i = 3; i < m_ServerSpawnedPrefabInstances.Count; i++) + foreach (var obj in autoSyncInactive) { - Assert.True(m_ServerSpawnedPrefabInstances[i].gameObject.scene == m_ScenesLoaded[1], - $"{m_ServerSpawnedPrefabInstances[i].gameObject.name} did not migrate into scene {m_ScenesLoaded[1].name} but are in scene {m_ServerSpawnedPrefabInstances[i].gameObject.scene.name}!"); + Assert.True(obj.gameObject.scene == newActiveScene, $"{obj.gameObject.name} did not migrate into scene {newActiveScene.name} but are in scene {obj.gameObject.scene.name}!"); } // Verify all NetworkObjects that should have been destroyed with the scene unloaded were destroyed @@ -414,18 +410,14 @@ public IEnumerator ActiveSceneSynchronizationTest() // Now unload the active scene to verify all remaining NetworkObjects are migrated into the SceneManager // assigned active scene - m_ClientsUnloadedScene = false; - m_CurrentSceneUnloading = m_ScenesLoaded[1].name; - m_ServerNetworkManager.SceneManager.UnloadScene(m_ScenesLoaded[1]); - yield return WaitForConditionOrTimeOut(() => m_ClientsUnloadedScene); - AssertOnTimeout($"Timed out waiting for all clients to unload scene {m_CurrentSceneUnloading}!"); + yield return UnloadScene(authority, newActiveScene); // Clean up any destroyed NetworkObjects - for (int i = m_ServerSpawnedPrefabInstances.Count - 1; i >= 0; i--) + for (int i = authoritySpawnedInstances.Count - 1; i >= 0; i--) { - if (m_ServerSpawnedPrefabInstances[i] == null) + if (authoritySpawnedInstances[i] == null) { - m_ServerSpawnedPrefabInstances.RemoveAt(i); + authoritySpawnedInstances.RemoveAt(i); } } @@ -433,22 +425,63 @@ public IEnumerator ActiveSceneSynchronizationTest() yield return CreateAndStartNewClient(); // Verify the late joining client spawns all instances - yield return WaitForConditionOrTimeOut(VerifyAllClientsSpawnedInstances); - AssertOnTimeout($"Timed out waiting for all clients to spawn {nameof(NetworkObject)}s!"); + yield return WaitForSpawnedOnAllOrTimeOut(authoritySpawnedInstances); + AssertOnTimeout($"[Late Joined Client #2] Timed out waiting for all clients to spawn {nameof(NetworkObject)}s!"); // Verify the instances are in the correct scenes - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"[Late Joined Client #2] Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // All but 3 instances should be destroyed - Assert.True(m_ServerSpawnedPrefabInstances.Count == 3, $"{nameof(m_ServerSpawnedPrefabInstances)} still has a count of {m_ServerSpawnedPrefabInstances.Count} " + - $"NetworkObject instances!"); - Assert.True(m_ServerSpawnedDestroyWithSceneInstances.Count == 0, $"{nameof(m_ServerSpawnedDestroyWithSceneInstances)} still has a count of " + - $"{m_ServerSpawnedDestroyWithSceneInstances.Count} NetworkObject instances!"); - for (int i = 0; i < 3; i++) - { - Assert.True(m_ServerSpawnedPrefabInstances[i].gameObject.name.Contains(m_TestPrefab.gameObject.name), $"Expected {m_ServerSpawnedPrefabInstances[i].gameObject.name} to contain {m_TestPrefab.gameObject.name}!"); - } + Assert.IsEmpty(autoSyncActive.Where(obj => obj != null), $"All the NetworkObjects with {nameof(NetworkObject.ActiveSceneSynchronization)}=true should have been destroyed when the active scene was unloaded!"); + Assert.IsEmpty(m_ServerSpawnedDestroyWithSceneInstances.Where(obj => obj != null), $"All the NetworkObjects with {nameof(NetworkObject.DestroyWithScene)} should have been destroyed when the active scene was unloaded!"); + Assert.AreEqual(3, autoSyncInactive.Count(obj => obj != null), $"All the NetworkObjects with {nameof(NetworkObject.ActiveSceneSynchronization)}=false should have survived the active scene change!"); + } + + /// + /// Helper method to load a scene and wait for the OnLoadEventCompleted event to trigger. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private IEnumerator LoadScene(NetworkManager authority, string sceneToLoad) + { + m_LoadEventCompleted = false; + authority.SceneManager.OnLoadEventCompleted += OnLoadEventCompleted; + var loadStatus = authority.SceneManager.LoadScene(sceneToLoad, LoadSceneMode.Additive); + Assert.True(loadStatus == SceneEventProgressStatus.Started, $"Failed to start loading scene {sceneToLoad}! Return status: {loadStatus}"); + yield return WaitForConditionOrTimeOut(() => m_LoadEventCompleted); + // Remove subscription prior to potentially asserting. + authority.SceneManager.OnLoadEventCompleted -= OnLoadEventCompleted; + AssertOnTimeout($"Timed out waiting for all clients to load scene {sceneToLoad}!"); + } + + /// + /// Helper method to load a scene and wait for the OnUnloadEventCompleted event to trigger. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private IEnumerator UnloadScene(NetworkManager authority, Scene sceneToUnload) + { + m_UnloadEventCompleted = false; + authority.SceneManager.OnUnloadEventCompleted += OnUnloadEventCompleted; + authority.SceneManager.UnloadScene(sceneToUnload); + + // Always make sure the scene event has completed. Trying to check if the scenes are loaded as a metric can + // create edge case scenarios where the scene might have been just loaded but not processed during synchronization. + yield return WaitForConditionOrTimeOut(() => m_UnloadEventCompleted); + // Remove subscription prior to potentially asserting. + authority.SceneManager.OnUnloadEventCompleted -= OnUnloadEventCompleted; + AssertOnTimeout($"Timed out waiting for all clients to unload scene {sceneToUnload.name}!"); + } + + private bool m_LoadEventCompleted; + private void OnLoadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List clientsCompleted, List clientsTimedOut) + { + m_LoadEventCompleted = true; + } + + private bool m_UnloadEventCompleted; + private void OnUnloadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List clientsCompleted, List clientsTimedOut) + { + m_UnloadEventCompleted = true; } /// @@ -462,30 +495,16 @@ private void OnNonActiveSynchDestroyWithSceneNetworkObjectDestroyed(NetworkObjec private void SceneManager_OnSceneEvent(SceneEvent sceneEvent) { + var authority = GetAuthorityNetworkManager(); switch (sceneEvent.SceneEventType) { case SceneEventType.LoadComplete: { - if (sceneEvent.ClientId == m_ServerNetworkManager.LocalClientId) + if (sceneEvent.ClientId == authority.LocalClientId) { - m_SceneLoaded = sceneEvent.Scene; m_ScenesLoaded.Add(sceneEvent.Scene); } - break; - } - case SceneEventType.LoadEventCompleted: - { - Assert.IsTrue(sceneEvent.ClientsThatTimedOut.Count == 0, $"{sceneEvent.ClientsThatTimedOut.Count} clients timed out while trying to load scene {m_CurrentSceneLoading}!"); - m_ClientsLoadedScene = true; - break; - } - case SceneEventType.UnloadEventCompleted: - { - if (sceneEvent.SceneName == m_CurrentSceneUnloading) - { - m_ClientsUnloadedScene = true; - } - break; + return; } } } @@ -495,12 +514,51 @@ protected override IEnumerator OnTearDown() m_TestPrefab = null; m_TestPrefabAutoSynchActiveScene = null; m_TestPrefabDestroyWithScene = null; + // Any static event that could be subscribed to but not unsubscribed to due to an assert needs to be cleaned up here. + DestroyWithSceneInstancesTestHelper.NetworkObjectDestroyed -= OnNonActiveSynchDestroyWithSceneNetworkObjectDestroyed; SceneManager.SetActiveScene(m_OriginalActiveScene); m_ServerSpawnedDestroyWithSceneInstances.Clear(); m_ServerSpawnedPrefabInstances.Clear(); m_ScenesLoaded.Clear(); yield return base.OnTearDown(); } + + private NetworkObject GetAuthorityInstance(NetworkObject instance) + { + if (instance.IsOwner) + { + return instance; + } + + var owner = instance.OwnerClientId; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.LocalClientId == owner) + { + networkManager.SpawnManager.SpawnedObjects.TryGetValue(instance.NetworkObjectId, out var networkObject); + return networkObject; + } + } + + return null; + } + } + + internal class SceneOriginTracker : NetworkBehaviour + { + public NetworkSceneHandle SceneWhereAwakeHappened; + private void Awake() + { + SceneWhereAwakeHappened = gameObject.scene.handle; + } + } + + internal class ShouldNeverSpawn : NetworkBehaviour + { + public override void OnNetworkSpawn() + { + Assert.Fail("Should never spawn!"); + } } /// @@ -509,7 +567,7 @@ protected override IEnumerator OnTearDown() /// internal class DestroyWithSceneInstancesTestHelper : NetworkBehaviour { - public static GameObject ShouldNeverSpawn; + public static ShouldNeverSpawn ShouldNeverSpawn; public static Dictionary> ObjectRelativeInstances = new Dictionary>(); @@ -544,12 +602,9 @@ public override void OnNetworkDespawn() public override void OnDestroy() { - if (NetworkManager != null) + if (IsSpawned && HasAuthority) { - if (NetworkManager.LocalClientId == NetworkManager.ServerClientId) - { - NetworkObjectDestroyed?.Invoke(NetworkObject); - } + NetworkObjectDestroyed?.Invoke(NetworkObject); } base.OnDestroy(); } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs new file mode 100644 index 0000000000..28cd0b49d2 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs @@ -0,0 +1,205 @@ +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; + +namespace TestProject.RuntimeTests +{ + /// + /// Validates client synchronization. + /// + /// + /// This includes both client synchronization mode passes along with verifying + /// that s migrated into the DDOL will still be spawned + /// and preserve their value. + /// + internal class NetworkSceneManagerStartupTests : NetcodeIntegrationTest + { + private const string k_ActiveScene = "SessionSynchronize"; + private const string k_AdditionalScene = "InSceneNetworkObjectMovesToDDOL"; + + private readonly List m_ObjectsInScenes = new List(); + private Scene m_OriginalActiveScene; + private Scene m_SceneLoaded; + private bool m_CanStart = false; + + // Used with scene pre-loading + private string m_SceneToLoad; + private bool m_SceneWasLoaded; + + #region NetcodeIntegrationTest overrides + protected override int NumberOfClients => 0; + protected override bool CanStartServerAndClients() => m_CanStart; + + protected override void OnOneTimeSetup() + { + // Get the active scene prior to any interation running through the OnSetup. + m_OriginalActiveScene = SceneManager.GetActiveScene(); + base.OnOneTimeSetup(); + } + + protected override IEnumerator OnSetup() + { + // Always reset + m_CanStart = false; + return base.OnSetup(); + } + + protected override void OnCreatePlayerPrefab() + { + // Avoid trying to spawn this + Object.DontDestroyOnLoad(m_PlayerPrefab); + base.OnCreatePlayerPrefab(); + } + + protected override IEnumerator OnTearDown() + { + LogAssert.ignoreFailingMessages = false; + m_ObjectsInScenes.Clear(); + // Restore the integration test scene as the active scene. + SceneManager.SetActiveScene(m_OriginalActiveScene); + + // Unload everything else. + for (int i = 0; i < SceneManager.sceneCount - 1; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (scene == m_OriginalActiveScene) + { + continue; + } + SceneManager.UnloadSceneAsync(scene); + yield return WaitForConditionOrTimeOut(() => !scene.isLoaded); + } + yield return base.OnTearDown(); + } + #endregion + + /// + /// Validates things migrated into the DDOL will be included when synchronizing clients. + /// + /// The client synchronization mode to use for the current pass. + [UnityTest] + public IEnumerator AllExistingObjectsAreSpawnedAtStartup([Values] LoadSceneMode clientSynchronizationMode) + { + LogAssert.ignoreFailingMessages = true; + yield return PreLoadScene(k_ActiveScene); + yield return PreLoadScene(k_AdditionalScene); + SceneManager.SetActiveScene(m_SceneLoaded); + + var existingObjects = new List(); + var dontDestroyOnLoadCount = 0; + + // Now get everything migrated into the DDOL and the DDOL scene itself. + var ddolScene = GetNetworkObjectsInDDOL(); + // Validate NetworkObjects in DDOL + foreach (var obj in m_ObjectsInScenes) + { + Assert.IsFalse(obj.IsSpawned, $"NetworkObject {obj.name} should not have been spawned!"); + + existingObjects.Add(obj); + if (obj.gameObject.scene.name == ddolScene.name) + { + dontDestroyOnLoadCount++; + } + } + + Assert.IsNotEmpty(existingObjects, $"Found no existing {nameof(NetworkObject)}s!"); + Assert.That(dontDestroyOnLoadCount, Is.GreaterThan(0), "Found no {nameof(NetworkObject)}s in the DDOL scene!"); + + // Now enable starting server and clients and start the server + m_CanStart = true; + yield return StartServerAndClients(); + + // Apply the test's client synchronization mode + GetAuthorityNetworkManager().SceneManager.SetClientSynchronizationMode(clientSynchronizationMode); + + // Validate the existing objects + foreach (var existingObject in existingObjects) + { + Assert.IsFalse(existingObject == null, "Expected existing object to still exist!"); + Assert.IsTrue(existingObject.IsSpawned, $"NetworkObject {existingObject.name} in scene {existingObject.gameObject.scene.name} was not spawned!"); + Assert.IsTrue(existingObject.InScenePlaced, $"NetworkObject {existingObject.name} in scene {existingObject.gameObject.scene.name} was not inScenePlaced!"); + } + + // If additive client synchronization mode, load the scenes that are already loaded + // on the scene authority instance so they will be used during client synchronization. + if (clientSynchronizationMode == LoadSceneMode.Additive) + { + yield return PreLoadScene(k_ActiveScene); + yield return PreLoadScene(k_AdditionalScene); + } + + // Late join a client + yield return CreateAndStartNewClient(); + + // Wait for all existing objects to spawn on the client + yield return WaitForSpawnedOnAllOrTimeOut(existingObjects); + AssertOnTimeout("Timed out waiting for objects to spawn on all clients!"); + } + + #region Scene loading and related methods + + /// + /// Uses the 's current scene which should be + /// the DDOL scene. + /// + /// The DDOL scene + private Scene GetNetworkObjectsInDDOL() + { + // This does catch any newly instantiated in-scene placed NetworkObjects moved into DDOL + // during awake. + var sceneToUse = NetworkManager.Singleton.gameObject.scene; + Assert.IsTrue(sceneToUse.IsValid() && sceneToUse.name == "DontDestroyOnLoad", $"[{NetworkManager.Singleton.name}] Is not in the DDOL! Is this being invoked too early?"); + + foreach (var rootObject in sceneToUse.GetRootGameObjects()) + { + foreach (var networkObject in rootObject.GetComponentsInChildren()) + { + if (!m_ObjectsInScenes.Contains(networkObject) && networkObject.InScenePlaced) + { + m_ObjectsInScenes.Add(networkObject); + } + } + } + return sceneToUse; + } + + private IEnumerator PreLoadScene(string sceneName) + { + m_SceneToLoad = sceneName; + m_SceneWasLoaded = false; + SceneManager.sceneLoaded += OnSceneLoad; + SceneManager.LoadScene(sceneName, LoadSceneMode.Additive); + yield return WaitForConditionOrTimeOut(() => m_SceneWasLoaded); + AssertOnTimeout("Timed out waiting for scene to load!"); + SceneManager.sceneLoaded -= OnSceneLoad; + } + + private void TrackObjectsInScene(Scene scene) + { + // This does not catch things moved into the DDOL during awake. + foreach (var rootObject in scene.GetRootGameObjects()) + { + foreach (var networkObject in rootObject.GetComponentsInChildren()) + { + m_ObjectsInScenes.Add(networkObject); + } + } + } + + private void OnSceneLoad(Scene scene, LoadSceneMode loadSceneMode) + { + if (m_SceneToLoad == scene.name) + { + m_SceneWasLoaded = true; + m_SceneLoaded = scene; + TrackObjectsInScene(scene); + } + } + #endregion + } +} diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs.meta new file mode 100644 index 0000000000..ddf1a7550e --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ca8e58d04b6fc6c48a6661c90d4c12dd \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs deleted file mode 100644 index 4f3c651cb3..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs +++ /dev/null @@ -1,95 +0,0 @@ -// using System.Collections; -// using NUnit.Framework; -// using Unity.Netcode.Components; -// using Unity.Netcode.Samples; -// using UnityEngine; -// using UnityEngine.TestTools; -// -// // Tests for ClientNetworkTransform (CNT) + NetworkRigidbody. This test is in TestProject because it needs access to ClientNetworkTransform -// namespace Unity.Netcode.RuntimeTests -// { -// public class NetworkRigidbody2DDynamicCntChangeOwnershipTest : NetworkRigidbody2DCntChangeOwnershipTestBase -// { -// public override bool Kinematic => false; -// } -// -// public class NetworkRigidbody2DKinematicCntChangeOwnershipTest : NetworkRigidbody2DCntChangeOwnershipTestBase -// { -// public override bool Kinematic => true; -// } -// -// public abstract class NetworkRigidbody2DCntChangeOwnershipTestBase : NetcodeIntegrationTest -// { -// protected override int NumberOfClients => 1; -// -// public abstract bool Kinematic { get; } -// -// [UnitySetUp] -// public override IEnumerator Setup() -// { -// yield return StartSomeClientsAndServerWithPlayers(true, NumberOfClients, playerPrefab => -// { -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.GetComponent().isKinematic = Kinematic; -// }); -// } -// /// -// /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. -// /// -// /// -// [UnityTest] -// public IEnumerator TestRigidbodyKinematicEnableDisable() -// { -// // This is the *SERVER VERSION* of the *CLIENT PLAYER* -// var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult); -// var serverPlayer = serverClientPlayerResult.Result.gameObject; -// -// // This is the *CLIENT VERSION* of the *CLIENT PLAYER* -// var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult); -// var clientPlayer = clientClientPlayerResult.Result.gameObject; -// -// Assert.IsNotNull(serverPlayer); -// Assert.IsNotNull(clientPlayer); -// -// int waitFor = Time.frameCount + 2; -// yield return new WaitUntil(() => Time.frameCount >= waitFor); -// -// TestKinematicSetCorrectly(clientPlayer, serverPlayer); -// -// -// // give server ownership over the player -// -// serverPlayer.GetComponent().ChangeOwnership(NetworkManager.ServerClientId); -// -// yield return null; -// yield return null; -// -// // server should now be able to commit to transform -// TestKinematicSetCorrectly(serverPlayer, clientPlayer); -// -// // return ownership to client -// serverPlayer.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); -// yield return null; -// yield return null; -// -// // client should again be able to commit -// TestKinematicSetCorrectly(clientPlayer, serverPlayer); -// } -// -// -// -// private void TestKinematicSetCorrectly(GameObject canCommitPlayer, GameObject canNotCommitPlayer) -// { -// -// // can commit player has authority and should have a kinematic mode of false (or true in case body was already kinematic). -// Assert.True(canCommitPlayer.GetComponent().isKinematic == Kinematic); -// -// // can not commit player has no authority and should have a kinematic mode of true -// Assert.True(canNotCommitPlayer.GetComponent().isKinematic); -// } -// } -// } diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs.meta b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs.meta deleted file mode 100644 index b1c3736256..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a51c7f7b93f04e3499089963d9bbb254 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs deleted file mode 100644 index eac6d42d7e..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs +++ /dev/null @@ -1,82 +0,0 @@ -// using System.Collections; -// using NUnit.Framework; -// using Unity.Netcode.Components; -// using Unity.Netcode.Samples; -// using UnityEngine; -// using UnityEngine.TestTools; -// -// // Tests for ClientNetworkTransform (CNT) + NetworkRigidbody2D. This test is in TestProject because it needs access to ClientNetworkTransform -// namespace Unity.Netcode.RuntimeTests -// { -// public class NetworkRigidbody2DDynamicCntTest : NetworkRigidbody2DCntTestBase -// { -// public override bool Kinematic => false; -// } -// -// public class NetworkRigidbody2DKinematicCntTest : NetworkRigidbody2DCntTestBase -// { -// public override bool Kinematic => true; -// } -// -// public abstract class NetworkRigidbody2DCntTestBase : NetcodeIntegrationTest -// { -// protected override int NumberOfClients => 1; -// -// public abstract bool Kinematic { get; } -// -// [UnitySetUp] -// public override IEnumerator Setup() -// { -// yield return StartSomeClientsAndServerWithPlayers(true, NumberOfClients, playerPrefab => -// { -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.GetComponent().isKinematic = Kinematic; -// }); -// } -// -// /// -// /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. -// /// -// /// -// [UnityTest] -// public IEnumerator TestRigidbodyKinematicEnableDisable() -// { -// // This is the *SERVER VERSION* of the *CLIENT PLAYER* -// var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult); -// var serverPlayer = serverClientPlayerResult.Result.gameObject; -// -// // This is the *CLIENT VERSION* of the *CLIENT PLAYER* -// var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult); -// var clientPlayer = clientClientPlayerResult.Result.gameObject; -// -// Assert.IsNotNull(serverPlayer); -// Assert.IsNotNull(clientPlayer); -// -// int waitFor = Time.frameCount + 2; -// yield return new WaitUntil(() => Time.frameCount >= waitFor); -// -// TestKinematicSetCorrectly(clientPlayer, serverPlayer); -// -// // despawn the server player -// serverPlayer.GetComponent().Despawn(false); -// -// yield return null; -// yield return null; -// Assert.IsTrue(clientPlayer == null); // safety check that object is actually despawned. -// } -// -// private void TestKinematicSetCorrectly(GameObject canCommitPlayer, GameObject canNotCommitPlayer) -// { -// -// // can commit player has authority and should have a kinematic mode of false (or true in case body was already kinematic). -// Assert.True(canCommitPlayer.GetComponent().isKinematic == Kinematic); -// -// // can not commit player has no authority and should have a kinematic mode of true -// Assert.True(canNotCommitPlayer.GetComponent().isKinematic); -// } -// } -// } diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs.meta b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs.meta deleted file mode 100644 index f0d4cff600..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5ae5587c06cba7a46b93ee6774741c0d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs deleted file mode 100644 index 2e45162f52..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs +++ /dev/null @@ -1,95 +0,0 @@ -// using System.Collections; -// using NUnit.Framework; -// using Unity.Netcode.Components; -// using Unity.Netcode.Samples; -// using UnityEngine; -// using UnityEngine.TestTools; -// -// // Tests for ClientNetworkTransform (CNT) + NetworkRigidbody. This test is in TestProject because it needs access to ClientNetworkTransform -// namespace Unity.Netcode.RuntimeTests -// { -// public class NetworkRigidbodyDynamicCntChangeOwnershipTest : NetworkRigidbodyCntChangeOwnershipTestBase -// { -// public override bool Kinematic => false; -// } -// -// public class NetworkRigidbodyKinematicCntChangeOwnershipTest : NetworkRigidbodyCntChangeOwnershipTestBase -// { -// public override bool Kinematic => true; -// } -// -// public abstract class NetworkRigidbodyCntChangeOwnershipTestBase : NetcodeIntegrationTest -// { -// protected override int NumberOfClients => 1; -// -// public abstract bool Kinematic { get; } -// -// [UnitySetUp] -// public override IEnumerator Setup() -// { -// yield return StartSomeClientsAndServerWithPlayers(true, NumberOfClients, playerPrefab => -// { -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.GetComponent().isKinematic = Kinematic; -// }); -// } -// /// -// /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. -// /// -// /// -// [UnityTest] -// public IEnumerator TestRigidbodyKinematicEnableDisable() -// { -// // This is the *SERVER VERSION* of the *CLIENT PLAYER* -// var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult); -// var serverPlayer = serverClientPlayerResult.Result.gameObject; -// -// // This is the *CLIENT VERSION* of the *CLIENT PLAYER* -// var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult); -// var clientPlayer = clientClientPlayerResult.Result.gameObject; -// -// Assert.IsNotNull(serverPlayer); -// Assert.IsNotNull(clientPlayer); -// -// int waitFor = Time.frameCount + 2; -// yield return new WaitUntil(() => Time.frameCount >= waitFor); -// -// TestKinematicSetCorrectly(clientPlayer, serverPlayer); -// -// -// // give server ownership over the player -// -// serverPlayer.GetComponent().ChangeOwnership(NetworkManager.ServerClientId); -// -// yield return null; -// yield return null; -// -// // server should now be able to commit to transform -// TestKinematicSetCorrectly(serverPlayer, clientPlayer); -// -// // return ownership to client -// serverPlayer.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); -// yield return null; -// yield return null; -// -// // client should again be able to commit -// TestKinematicSetCorrectly(clientPlayer, serverPlayer); -// } -// -// -// -// private void TestKinematicSetCorrectly(GameObject canCommitPlayer, GameObject canNotCommitPlayer) -// { -// -// // can commit player has authority and should have a kinematic mode of false (or true in case body was already kinematic). -// Assert.True(canCommitPlayer.GetComponent().isKinematic == Kinematic); -// -// // can not commit player has no authority and should have a kinematic mode of true -// Assert.True(canNotCommitPlayer.GetComponent().isKinematic); -// } -// } -// } diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs.meta b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs.meta deleted file mode 100644 index 3eb4f88f5b..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 941008c8040f03c44bd835d24b073260 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs deleted file mode 100644 index 72f0c72b96..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs +++ /dev/null @@ -1,82 +0,0 @@ -// using System.Collections; -// using NUnit.Framework; -// using Unity.Netcode.Components; -// using Unity.Netcode.Samples; -// using UnityEngine; -// using UnityEngine.TestTools; -// -// // Tests for ClientNetworkTransform (CNT) + NetworkRigidbody. This test is in TestProject because it needs access to ClientNetworkTransform -// namespace Unity.Netcode.RuntimeTests -// { -// public class NetworkRigidbodyDynamicCntTest : NetworkRigidbodyCntTestBase -// { -// public override bool Kinematic => false; -// } -// -// public class NetworkRigidbodyKinematicCntTest : NetworkRigidbodyCntTestBase -// { -// public override bool Kinematic => true; -// } -// -// public abstract class NetworkRigidbodyCntTestBase : NetcodeIntegrationTest -// { -// protected override int NumberOfClients => 1; -// -// public abstract bool Kinematic { get; } -// -// [UnitySetUp] -// public override IEnumerator Setup() -// { -// yield return StartSomeClientsAndServerWithPlayers(true, NumberOfClients, playerPrefab => -// { -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.GetComponent().isKinematic = Kinematic; -// }); -// } -// /// -// /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. -// /// -// /// -// [UnityTest] -// public IEnumerator TestRigidbodyKinematicEnableDisable() -// { -// // This is the *SERVER VERSION* of the *CLIENT PLAYER* -// var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield returnNetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult); -// var serverPlayer = serverClientPlayerResult.Result.gameObject; -// -// // This is the *CLIENT VERSION* of the *CLIENT PLAYER* -// var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult); -// var clientPlayer = clientClientPlayerResult.Result.gameObject; -// -// Assert.IsNotNull(serverPlayer); -// Assert.IsNotNull(clientPlayer); -// -// int waitFor = Time.frameCount + 2; -// yield return new WaitUntil(() => Time.frameCount >= waitFor); -// -// TestKinematicSetCorrectly(clientPlayer, serverPlayer); -// -// // despawn the server player -// serverPlayer.GetComponent().Despawn(false); -// -// yield return null; -// yield return null; -// -// Assert.IsTrue(clientPlayer == null); // safety check that object is actually despawned. -// } -// -// private void TestKinematicSetCorrectly(GameObject canCommitPlayer, GameObject canNotCommitPlayer) -// { -// -// // can commit player has authority and should have a kinematic mode of false (or true in case body was already kinematic). -// Assert.True(canCommitPlayer.GetComponent().isKinematic == Kinematic); -// -// // can not commit player has no authority and should have a kinematic mode of true -// Assert.True(canNotCommitPlayer.GetComponent().isKinematic); -// } -// } -// } diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs.meta b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs.meta deleted file mode 100644 index cea97efc01..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e89712f9406aaa84d85508fa07d97655 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs index c7e79157b0..9760465f7e 100644 --- a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs +++ b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs @@ -1,6 +1,5 @@ using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Text; using NUnit.Framework; using TestProject.ManualTests; @@ -217,9 +216,6 @@ public enum InstantiateAndSpawnMethods [UnityTest] public IEnumerator TestPrefabsSpawning([Values] InstantiateAndSpawnMethods instantiateAndSpawnType) { - var gloabalObjectId = m_SceneManagementEnabled ? 0 : InScenePlacedHelper.ServerInSceneDefined.First().GlobalObjectIdHash; - var firstError = $"[Netcode] Failed to create object locally. [globalObjectIdHash={gloabalObjectId}]. NetworkPrefab could not be found. Is the prefab registered with NetworkManager?"; - var secondError = $"[Netcode] Failed to spawn NetworkObject for Hash {gloabalObjectId}."; m_InstantiateAndSpawnType = instantiateAndSpawnType; // We have to spawn the first client manually in order to account for the errors when scene management is disabled. @@ -228,7 +224,6 @@ public IEnumerator TestPrefabsSpawning([Values] InstantiateAndSpawnMethods insta // spawn the original prefab and when spawning dynamically the override is used. yield return CreateAndStartNewClient(); - var spawnManager = m_ServerNetworkManager.SpawnManager; // If scene management is enabled, then we want to verify against the editor // assigned in-scene placed NetworkObjects if (m_SceneManagementEnabled) diff --git a/testproject/ProjectSettings/EditorBuildSettings.asset b/testproject/ProjectSettings/EditorBuildSettings.asset index b2632cd9f5..63b5a1481e 100644 --- a/testproject/ProjectSettings/EditorBuildSettings.asset +++ b/testproject/ProjectSettings/EditorBuildSettings.asset @@ -44,9 +44,6 @@ EditorBuildSettings: - enabled: 1 path: Assets/Tests/Manual/NetworkSceneManagerCallbacks/SceneWeAreSwitchingFrom.unity guid: 073bd2111475c0643be45b7abe6a97ad - - enabled: 1 - path: Assets/Scenes/MultiprocessTestScene.unity - guid: 76743cb7b342c49279327834918a9c6e - enabled: 1 path: Assets/Scenes/EmptyScene.unity guid: a2545a872c007404fbb6b0393ab74974 @@ -133,6 +130,9 @@ EditorBuildSettings: guid: 17b92153f7381d34fa48c4d5c0393d13 - enabled: 1 path: Assets/Tests/Manual/IntegrationTestScenes/EmptyScene3.unity + guid: bfb4addb64cf6455a88668eba2f759ba + - enabled: 1 + path: Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity guid: abd4c8b51c445d54faa16c67ac973f1b - enabled: 1 path: Assets/Samples/SpawnObject/SimpleSpawn.unity @@ -158,7 +158,9 @@ EditorBuildSettings: - enabled: 1 path: Assets/Tests/Manual/NetworkAnimatorTests/AnimationBidirectionalTriggers/NetworkAnimatorDualTriggerCheer.unity guid: e12df855278120245a8a936a6a52b5bd + - enabled: 1 + path: Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity + guid: 4c20c17b4f92e634f8796b0460851d49 m_configObjects: com.unity.addressableassets: {fileID: 11400000, guid: 5a3d5c53c25349c48912726ae850f3b0, type: 2} - m_UseUCBPForAssetBundles: 0