diff --git a/.yamato/_triggers.yml b/.yamato/_triggers.yml
index 6f2da59265..4048112495 100644
--- a/.yamato/_triggers.yml
+++ b/.yamato/_triggers.yml
@@ -159,6 +159,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 +191,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..6b6b354b18 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.86.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.86.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.86.0
flavor: b1.large
smaller_flavor: b1.large
larger_flavor: b1.xlarge
@@ -177,8 +177,8 @@ validation_editors:
- 6000.3
all:
- 6000.3
- - 6000.4
- 6000.5
+ - 6000.6
- trunk
- f1298548e194f35ff7dfa6fee699d4464ab3919c
minimal:
diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml
index 6976c10973..1b9106d777 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml
index 06c750a127..fec8ec8847 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.0.0
diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml
index 445f507a73..1029bfeb24 100644
--- a/.yamato/wrench/preview-a-p-v.yml
+++ b/.yamato/wrench/preview-a-p-v.yml
@@ -22,12 +22,15 @@ all_preview_apv_jobs:
- 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.0.0
# Functional tests for dependents found in the latest 6000.0 manifest (MacOS).
preview_apv_-_6000_0_-_macos13:
@@ -82,10 +85,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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu).
preview_apv_-_6000_0_-_ubuntu2204:
@@ -140,10 +143,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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.0 manifest (Windows).
preview_apv_-_6000_0_-_win10:
@@ -199,10 +202,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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.3 manifest (MacOS).
preview_apv_-_6000_3_-_macos13:
@@ -257,10 +260,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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.3 manifest (Ubuntu).
preview_apv_-_6000_3_-_ubuntu2204:
@@ -315,10 +318,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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.3 manifest (Windows).
preview_apv_-_6000_3_-_win10:
@@ -374,10 +377,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.4 manifest (MacOS).
preview_apv_-_6000_4_-_macos13:
@@ -432,10 +435,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.4 manifest (Ubuntu).
preview_apv_-_6000_4_-_ubuntu2204:
@@ -490,10 +493,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.4 manifest (Windows).
preview_apv_-_6000_4_-_win10:
@@ -549,10 +552,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.5 manifest (MacOS).
preview_apv_-_6000_5_-_macos13:
@@ -607,10 +610,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.5 manifest (Ubuntu).
preview_apv_-_6000_5_-_ubuntu2204:
@@ -665,10 +668,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.5 manifest (Windows).
preview_apv_-_6000_5_-_win10:
@@ -724,18 +727,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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.6 manifest (MacOS).
-preview_apv_-_6000_6_-_macos13:
- name: Preview APV - 6000.6 - macos13
+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
@@ -744,7 +748,7 @@ 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 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.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~
@@ -782,10 +786,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.6 manifest (Ubuntu).
preview_apv_-_6000_6_-_ubuntu2204:
@@ -802,7 +806,7 @@ preview_apv_-_6000_6_-_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 trunk -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.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~
@@ -840,10 +844,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Functional tests for dependents found in the latest 6000.6 manifest (Windows).
preview_apv_-_6000_6_-_win10:
@@ -861,7 +865,7 @@ preview_apv_-_6000_6_-_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 trunk -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.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~
@@ -899,8 +903,184 @@ 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.0.0
+ metadata:
+ Job Maintainers: '#rm-packageworks'
+ Wrench: 3.2.0.0
+
+# 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-arm64:v4
+ type: Unity::VM::osx
+ 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: 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 --arch arm64
+ timeout: 10
+ retries: 3
+ - 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
+ artifacts:
+ Crash Dumps:
+ paths:
+ - CrashDumps/**
+ logs:
+ paths:
+ - '*.log'
+ - '*.xml'
+ - upm-ci~/test-results/**/*
+ - upm-ci~/temp/*/Logs/**
+ - upm-ci~/temp/*/Library/*.log
+ - upm-ci~/temp/*/*.log
+ - upm-ci~/temp/Builds/*.log
+ packages:
+ paths:
+ - upm-ci~/packages/**/*
+ PreviewAPVResults:
+ paths:
+ - PreviewApvArtifacts~/**
+ - APVTest/**/manifest.json
+ pvp-results:
+ paths:
+ - upm-ci~/pvp/**/*
+ browsable: onDemand
+ dependencies:
+ - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects
+ variables:
+ UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/
+ UNITY_LICENSING_SERVER_DELETE_NUL: 0
+ UNITY_LICENSING_SERVER_DELETE_ULF: 0
+ UNITY_LICENSING_SERVER_TOOLSET: pro
+ UPMPVP_CONTEXT_WRENCH: 3.2.0.0
+ metadata:
+ Job Maintainers: '#rm-packageworks'
+ Wrench: 3.2.0.0
+
+# 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
+ flavor: b1.large
+ 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: 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
+ timeout: 10
+ retries: 3
+ - 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
+ artifacts:
+ Crash Dumps:
+ paths:
+ - CrashDumps/**
+ logs:
+ paths:
+ - '*.log'
+ - '*.xml'
+ - upm-ci~/test-results/**/*
+ - upm-ci~/temp/*/Logs/**
+ - upm-ci~/temp/*/Library/*.log
+ - upm-ci~/temp/*/*.log
+ - upm-ci~/temp/Builds/*.log
+ packages:
+ paths:
+ - upm-ci~/packages/**/*
+ PreviewAPVResults:
+ paths:
+ - PreviewApvArtifacts~/**
+ - APVTest/**/manifest.json
+ pvp-results:
+ paths:
+ - upm-ci~/pvp/**/*
+ browsable: onDemand
+ dependencies:
+ - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects
+ variables:
+ UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/
+ UNITY_LICENSING_SERVER_DELETE_NUL: 0
+ UNITY_LICENSING_SERVER_DELETE_ULF: 0
+ UNITY_LICENSING_SERVER_TOOLSET: pro
+ UPMPVP_CONTEXT_WRENCH: 3.2.0.0
+ metadata:
+ Job Maintainers: '#rm-packageworks'
+ Wrench: 3.2.0.0
+
+# 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
+ flavor: b1.large
+ commands:
+ - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f
+ - 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: 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
+ timeout: 10
+ retries: 3
+ - 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:
+ Crash Dumps:
+ paths:
+ - CrashDumps/**
+ logs:
+ paths:
+ - '*.log'
+ - '*.xml'
+ - upm-ci~/test-results/**/*
+ - upm-ci~/temp/*/Logs/**
+ - upm-ci~/temp/*/Library/*.log
+ - upm-ci~/temp/*/*.log
+ - upm-ci~/temp/Builds/*.log
+ packages:
+ paths:
+ - upm-ci~/packages/**/*
+ PreviewAPVResults:
+ paths:
+ - PreviewApvArtifacts~/**
+ - APVTest/**/manifest.json
+ pvp-results:
+ paths:
+ - upm-ci~/pvp/**/*
+ browsable: onDemand
+ dependencies:
+ - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects
+ variables:
+ UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/
+ UNITY_LICENSING_SERVER_DELETE_NUL: 0
+ UNITY_LICENSING_SERVER_DELETE_ULF: 0
+ UNITY_LICENSING_SERVER_TOOLSET: pro
+ UPMPVP_CONTEXT_WRENCH: 3.2.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml
index 7fd22a018d..929cce45a2 100644
--- a/.yamato/wrench/promotion-jobs.yml
+++ b/.yamato/wrench/promotion-jobs.yml
@@ -151,15 +151,15 @@ publish_dry_run_netcode_gameobjects:
UTR:
location: results/UTR/validate-netcode.gameobjects-6000.5-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_6_-_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.6-macos13arm
unzip: true
UTR:
- location: results/UTR/validate-netcode.gameobjects-6000.6-macos13
+ location: results/UTR/validate-netcode.gameobjects-6000.6-macos13arm
unzip: true
- path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204
specific_options:
@@ -181,12 +181,42 @@ publish_dry_run_netcode_gameobjects:
UTR:
location: results/UTR/validate-netcode.gameobjects-6000.6-win10
unzip: true
+ - 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.7-macos13arm
+ unzip: true
+ UTR:
+ location: results/UTR/validate-netcode.gameobjects-6000.7-macos13arm
+ unzip: true
+ - 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.7-ubuntu2204
+ unzip: true
+ UTR:
+ location: results/UTR/validate-netcode.gameobjects-6000.7-ubuntu2204
+ unzip: true
+ - 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.7-win10
+ unzip: true
+ UTR:
+ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
# Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm
publish_netcode_gameobjects:
@@ -333,15 +363,15 @@ publish_netcode_gameobjects:
UTR:
location: results/UTR/validate-netcode.gameobjects-6000.5-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_6_-_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.6-macos13arm
unzip: true
UTR:
- location: results/UTR/validate-netcode.gameobjects-6000.6-macos13
+ location: results/UTR/validate-netcode.gameobjects-6000.6-macos13arm
unzip: true
- path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204
specific_options:
@@ -363,11 +393,41 @@ publish_netcode_gameobjects:
UTR:
location: results/UTR/validate-netcode.gameobjects-6000.6-win10
unzip: true
+ - 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.7-macos13arm
+ unzip: true
+ UTR:
+ location: results/UTR/validate-netcode.gameobjects-6000.7-macos13arm
+ unzip: true
+ - 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.7-ubuntu2204
+ unzip: true
+ UTR:
+ location: results/UTR/validate-netcode.gameobjects-6000.7-ubuntu2204
+ unzip: true
+ - 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.7-win10
+ unzip: true
+ UTR:
+ 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.0.0
allow_on: branch match "^release/.*"
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml
index b9eba06e54..34d5f02642 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.0.0
diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml
index 84a92fcd75..9e05a163af 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
labels:
- Packages:netcode.gameobjects
@@ -419,10 +419,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
labels:
- Packages:netcode.gameobjects
@@ -489,10 +489,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
labels:
- Packages:netcode.gameobjects
@@ -559,10 +559,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
labels:
- Packages:netcode.gameobjects
@@ -629,10 +629,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
labels:
- Packages:netcode.gameobjects
@@ -699,10 +699,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
labels:
- Packages:netcode.gameobjects
@@ -769,10 +769,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
labels:
- Packages:netcode.gameobjects
@@ -839,26 +839,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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.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.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 trunk -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
@@ -909,10 +910,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
labels:
- Packages:netcode.gameobjects
@@ -928,7 +929,7 @@ validate_-_netcode_gameobjects_-_6000_6_-_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 trunk -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
@@ -979,10 +980,10 @@ 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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
labels:
- Packages:netcode.gameobjects
@@ -998,6 +999,217 @@ validate_-_netcode_gameobjects_-_6000_6_-_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.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
+ timeout: 10
+ retries: 1
+ - command: echo No internal packages to add.
+ - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp
+ timeout: 40
+ retries: 0
+ - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json
+ timeout: 5
+ retries: 0
+ - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json
+ timeout: 10
+ retries: 0
+ - command: 'UnifiedTestRunner.exe --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" '
+ timeout: 40
+ retries: 1
+ after:
+ - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd
+ artifacts:
+ Crash Dumps:
+ paths:
+ - CrashDumps/**
+ packages:
+ paths:
+ - upm-ci~/packages/**/*
+ pvp-results:
+ paths:
+ - upm-ci~/pvp/**/*
+ browsable: onDemand
+ UTR:
+ paths:
+ - '*.log'
+ - '*.xml'
+ - artifacts/**/*
+ - testproject/Logs/**
+ - testproject/Library/*.log
+ - testproject/*.log
+ - testproject/Builds/*.log
+ - build/test-results/**
+ browsable: onDemand
+ dependencies:
+ - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects
+ variables:
+ UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/
+ UNITY_LICENSING_SERVER_DELETE_NUL: 0
+ UNITY_LICENSING_SERVER_DELETE_ULF: 0
+ UNITY_LICENSING_SERVER_TOOLSET: pro
+ UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1
+ UPMPVP_CONTEXT_WRENCH: 3.2.0.0
+ metadata:
+ Job Maintainers: '#rm-packageworks'
+ Wrench: 3.2.0.0
+ labels:
+ - Packages:netcode.gameobjects
+
+# 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-arm64:v4
+ type: Unity::VM::osx
+ 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 --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
+ timeout: 10
+ retries: 1
+ - command: echo No internal packages to add.
+ - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp
+ timeout: 40
+ retries: 0
+ - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json
+ timeout: 5
+ retries: 0
+ - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json
+ timeout: 10
+ retries: 0
+ - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" '
+ timeout: 40
+ retries: 1
+ after:
+ - command: bash .yamato/generated-scripts/infrastructure-instability-detection-mac.sh
+ artifacts:
+ Crash Dumps:
+ paths:
+ - CrashDumps/**
+ packages:
+ paths:
+ - upm-ci~/packages/**/*
+ pvp-results:
+ paths:
+ - upm-ci~/pvp/**/*
+ browsable: onDemand
+ UTR:
+ paths:
+ - '*.log'
+ - '*.xml'
+ - artifacts/**/*
+ - testproject/Logs/**
+ - testproject/Library/*.log
+ - testproject/*.log
+ - testproject/Builds/*.log
+ - build/test-results/**
+ browsable: onDemand
+ dependencies:
+ - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects
+ variables:
+ UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/
+ UNITY_LICENSING_SERVER_DELETE_NUL: 0
+ UNITY_LICENSING_SERVER_DELETE_ULF: 0
+ UNITY_LICENSING_SERVER_TOOLSET: pro
+ UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1
+ UPMPVP_CONTEXT_WRENCH: 3.2.0.0
+ metadata:
+ Job Maintainers: '#rm-packageworks'
+ Wrench: 3.2.0.0
+ labels:
+ - Packages:netcode.gameobjects
+
+# 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
+ flavor: b1.large
+ 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
+ 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
+ timeout: 10
+ retries: 1
+ - command: echo No internal packages to add.
+ - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp
+ timeout: 40
+ retries: 0
+ - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json
+ timeout: 5
+ retries: 0
+ - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json
+ timeout: 10
+ retries: 0
+ - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" '
+ timeout: 40
+ retries: 1
+ after:
+ - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh
+ artifacts:
+ Crash Dumps:
+ paths:
+ - CrashDumps/**
+ packages:
+ paths:
+ - upm-ci~/packages/**/*
+ pvp-results:
+ paths:
+ - upm-ci~/pvp/**/*
+ browsable: onDemand
+ UTR:
+ paths:
+ - '*.log'
+ - '*.xml'
+ - artifacts/**/*
+ - testproject/Logs/**
+ - testproject/Library/*.log
+ - testproject/*.log
+ - testproject/Builds/*.log
+ - build/test-results/**
+ browsable: onDemand
+ dependencies:
+ - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects
+ variables:
+ UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/
+ UNITY_LICENSING_SERVER_DELETE_NUL: 0
+ UNITY_LICENSING_SERVER_DELETE_ULF: 0
+ UNITY_LICENSING_SERVER_TOOLSET: pro
+ UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1
+ UPMPVP_CONTEXT_WRENCH: 3.2.0.0
+ metadata:
+ Job Maintainers: '#rm-packageworks'
+ Wrench: 3.2.0.0
+ labels:
+ - Packages:netcode.gameobjects
+
+# 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
+ flavor: b1.large
+ 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
timeout: 20
retries: 3
@@ -1049,10 +1261,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.0.0
metadata:
Job Maintainers: '#rm-packageworks'
- Wrench: 2.9.0.0
+ Wrench: 3.2.0.0
labels:
- Packages:netcode.gameobjects
diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json
index 9aa8ecfde1..585d0dfb22 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.0.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..ef0ba3d3bc 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..c7488f99f8 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -30,6 +30,11 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Fixed
+- 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)
### Security
@@ -37,6 +42,21 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Obsolete
+## [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..47bd50c6bc 100644
--- a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md
+++ b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md
@@ -70,15 +70,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~/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..6d970c8d61 100644
--- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs
+++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs
@@ -2152,14 +2152,14 @@ private bool GetReadMethodForParameter(TypeReference paramType, out MethodRefere
}
else
#endif
- if (paramType.Resolve().FullName == "Unity.Collections.NativeArray`1")
- {
- typeMethod = GetFastBufferReaderReadMethod(k_ReadValueTempMethodName, paramType);
- }
- else
- {
- typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType);
- }
+ if (paramType.Resolve().FullName == "Unity.Collections.NativeArray`1")
+ {
+ typeMethod = GetFastBufferReaderReadMethod(k_ReadValueTempMethodName, paramType);
+ }
+ else
+ {
+ typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType);
+ }
if (typeMethod != null)
{
methodRef = m_MainModule.ImportReference(typeMethod);
diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs
index abf238c45a..f9802ac76a 100644
--- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs
+++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs
@@ -196,10 +196,10 @@ 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)
- {
- s_LastKnownNetworkManagerParents.Remove(networkManager);
- }
+ if (transform.root == gameObject.transform)
+ {
+ s_LastKnownNetworkManagerParents.Remove(networkManager);
+ }
}
if (!EditorApplication.isUpdating && isParented)
{
diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs
index 24eb580402..294cd9989f 100644
--- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs
@@ -468,19 +468,19 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime
}
}
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)
- {
- // 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),
- // we will want to reset the interpolator with the current known value. This prevents the next received state update's
- // time to be calculated against the last calculated time which if there is an extended period of time between the two
- // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then
- // starts moving again).
- if ((tickLatencyAsTime - InterpolateState.Target.Value.TimeSent) > InterpolateState.MaxDeltaTime + minDeltaTime)
+ if (m_BufferQueue.Count == 0)
{
- InterpolateState.Reset(InterpolateState.CurrentValue);
+ // 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),
+ // we will want to reset the interpolator with the current known value. This prevents the next received state update's
+ // time to be calculated against the last calculated time which if there is an extended period of time between the two
+ // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then
+ // starts moving again).
+ if ((tickLatencyAsTime - InterpolateState.Target.Value.TimeSent) > InterpolateState.MaxDeltaTime + minDeltaTime)
+ {
+ InterpolateState.Reset(InterpolateState.CurrentValue);
+ }
}
- }
}
m_NbItemsReceivedThisFrame = 0;
return InterpolateState.CurrentValue;
@@ -594,19 +594,19 @@ public T Update(float deltaTime, double renderTime, double serverTime)
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)
- {
- // 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),
- // we will want to reset the interpolator with the current known value. This prevents the next received state update's
- // time to be calculated against the last calculated time which if there is an extended period of time between the two
- // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then
- // starts moving again).
- if ((renderTime - InterpolateState.Target.Value.TimeSent) > 0.3f) // If we haven't recevied anything within 300ms, assume we stopped motion.
+ if (InterpolateState.TargetReached && m_BufferQueue.Count == 0)
{
- InterpolateState.Reset(InterpolateState.CurrentValue);
+ // 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),
+ // we will want to reset the interpolator with the current known value. This prevents the next received state update's
+ // time to be calculated against the last calculated time which if there is an extended period of time between the two
+ // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then
+ // starts moving again).
+ if ((renderTime - InterpolateState.Target.Value.TimeSent) > 0.3f) // If we haven't recevied anything within 300ms, assume we stopped motion.
+ {
+ InterpolateState.Reset(InterpolateState.CurrentValue);
+ }
}
- }
m_NbItemsReceivedThisFrame = 0;
return InterpolateState.CurrentValue;
}
diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs
index 246d6b8484..a31fb82286 100644
--- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs
@@ -1181,25 +1181,25 @@ internal void CheckForAnimatorChanges()
SendAnimStateRpc(m_AnimationMessage);
}
else
- if (!IsServer && IsOwner)
- {
- SendServerAnimStateRpc(m_AnimationMessage);
- }
- else
- {
- // Just notify all remote clients and not the local server
- m_TargetGroup.Clear();
- foreach (var clientId in LocalNetworkManager.ConnectionManager.ConnectedClientIds)
+ if (!IsServer && IsOwner)
{
- if (clientId == LocalNetworkManager.LocalClientId || !NetworkObject.Observers.Contains(clientId))
+ SendServerAnimStateRpc(m_AnimationMessage);
+ }
+ else
+ {
+ // Just notify all remote clients and not the local server
+ m_TargetGroup.Clear();
+ foreach (var clientId in LocalNetworkManager.ConnectionManager.ConnectedClientIds)
{
- continue;
+ if (clientId == LocalNetworkManager.LocalClientId || !NetworkObject.Observers.Contains(clientId))
+ {
+ continue;
+ }
+ m_TargetGroup.Add(clientId);
}
- m_TargetGroup.Add(clientId);
+ m_RpcParams.Send.Target = m_TargetGroup.Target;
+ SendClientAnimStateRpc(m_AnimationMessage, m_RpcParams);
}
- m_RpcParams.Send.Target = m_TargetGroup.Target;
- SendClientAnimStateRpc(m_AnimationMessage, m_RpcParams);
- }
}
}
diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs
index ff0636c5b4..bae10305f1 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_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Extrapolate : m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.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
+ }
+
[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..50a74ae9ad 100644
--- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs
@@ -2527,23 +2527,23 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is
}
}
else // Just apply the full local scale when synchronizing
- if (SynchronizeScale)
- {
- var localScale = CachedTransform.localScale;
- if (!UseHalfFloatPrecision)
+ if (SynchronizeScale)
{
+ var localScale = CachedTransform.localScale;
+ if (!UseHalfFloatPrecision)
+ {
- networkState.ScaleX = localScale.x;
- networkState.ScaleY = localScale.y;
- networkState.ScaleZ = localScale.z;
- }
- else
- {
- networkState.Scale = localScale;
+ networkState.ScaleX = localScale.x;
+ networkState.ScaleY = localScale.y;
+ networkState.ScaleZ = localScale.z;
+ }
+ else
+ {
+ networkState.Scale = localScale;
+ }
+ flagStates.MarkChanged(AxialType.Scale, true);
+ isScaleDirty = true;
}
- flagStates.MarkChanged(AxialType.Scale, true);
- isScaleDirty = true;
- }
isDirty |= isPositionDirty || isRotationDirty || isScaleDirty;
if (isDirty)
@@ -3500,11 +3500,11 @@ private void NonAuthorityFinalizeSynchronization()
}
}
else // Otherwise, just run through standard synchronization of this instance
- if (!CanCommitToTransform)
- {
- ApplySynchronization();
- InternalInitialization();
- }
+ if (!CanCommitToTransform)
+ {
+ ApplySynchronization();
+ InternalInitialization();
+ }
}
}
diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs
index 5a8724374c..dd22801412 100644
--- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs
@@ -968,18 +968,18 @@ internal void ProcessClientsToDisconnect()
return (true, playerPrefabHash.Value);
}
else
- if (NetworkManager.NetworkConfig.PlayerPrefab != null)
- {
- var networkObject = NetworkManager.NetworkConfig.PlayerPrefab.GetComponent();
- if (networkObject != null)
+ if (NetworkManager.NetworkConfig.PlayerPrefab != null)
{
- return (true, networkObject.GlobalObjectIdHash);
- }
- else
- {
- NetworkManager.Log.Error(new Logging.Context(LogLevel.Error, $"Player prefab {NetworkManager.NetworkConfig.PlayerPrefab.name} has no {nameof(NetworkObject)}!"));
+ var networkObject = NetworkManager.NetworkConfig.PlayerPrefab.GetComponent();
+ if (networkObject != null)
+ {
+ return (true, networkObject.GlobalObjectIdHash);
+ }
+ else
+ {
+ NetworkManager.Log.Error(new Logging.Context(LogLevel.Error, $"Player prefab {NetworkManager.NetworkConfig.PlayerPrefab.name} has no {nameof(NetworkObject)}!"));
+ }
}
- }
return (false, 0);
}
@@ -1218,7 +1218,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 e9c4fe4693..01490e3e8e 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
@@ -453,7 +454,7 @@ public void DeferDespawn(int tickOffset, bool destroy = true)
return;
}
- if (!HasAuthority)
+ if (!m_HasAuthority)
{
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
@@ -670,7 +671,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)
{
@@ -946,7 +947,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
{
@@ -1192,14 +1193,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.
@@ -1485,10 +1481,7 @@ public bool IsNetworkVisibleTo(ulong clientId)
///
internal Scene SceneOrigin
{
- get
- {
- return m_SceneOrigin;
- }
+ get => m_SceneOrigin;
set
{
@@ -1508,13 +1501,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;
}
@@ -1543,7 +1531,7 @@ public void NetworkShow(ulong clientId)
return;
}
- if (!HasAuthority)
+ if (!m_HasAuthority)
{
if (NetworkManagerOwner.DistributedAuthorityMode)
{
@@ -1638,7 +1626,7 @@ public void NetworkHide(ulong clientId)
return;
}
- if (!HasAuthority)
+ if (!m_HasAuthority)
{
if (NetworkManagerOwner.DistributedAuthorityMode)
{
@@ -1804,7 +1792,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;
@@ -2049,8 +2037,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);
}
@@ -2108,10 +2095,60 @@ 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;
+ 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;
+ }
+ }
+
internal void ResetOnDespawn()
{
// Always clear out the observers list when despawned
Observers.Clear();
+ m_HasAuthority = false;
IsSpawnAuthority = false;
IsSpawned = false;
DeferredDespawnTick = 0;
@@ -2119,6 +2156,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
///
@@ -2149,7 +2242,7 @@ public void ChangeOwnership(ulong newOwnerClientId)
}
return;
}
- NetworkManagerOwner.SpawnManager.ChangeOwnership(this, newOwnerClientId, HasAuthority);
+ NetworkManagerOwner.SpawnManager.ChangeOwnership(this, newOwnerClientId, m_HasAuthority);
}
///
@@ -2158,20 +2251,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);
@@ -2384,7 +2475,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.
@@ -2459,7 +2550,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)
{
@@ -2614,25 +2705,25 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa
return true;
}
else // If the parent still isn't spawned add this to the orphaned children and return false
- if (!parentNetworkObject.IsSpawned)
- {
- OrphanChildren.Add(this);
- return false;
- }
- else
- {
- // If we made it this far, go ahead and set the network parenting values
- // with the WorldPoisitonSays value set to false
- // Note: Since in-scene placed NetworkObjects are parented in the scene
- // the default "assumption" is that children are parenting local space
- // relative.
- SetNetworkParenting(parentNetworkObject.NetworkObjectId, false);
+ if (!parentNetworkObject.IsSpawned)
+ {
+ OrphanChildren.Add(this);
+ return false;
+ }
+ else
+ {
+ // If we made it this far, go ahead and set the network parenting values
+ // with the WorldPoisitonSays value set to false
+ // Note: Since in-scene placed NetworkObjects are parented in the scene
+ // the default "assumption" is that children are parenting local space
+ // relative.
+ SetNetworkParenting(parentNetworkObject.NetworkObjectId, false);
- // Set the cached parent
- SetCachedParent(parentNetworkObject.transform);
+ // Set the cached parent
+ SetCachedParent(parentNetworkObject.transform);
- return true;
- }
+ return true;
+ }
}
// If we are removing the parent or our latest parent is not set, then remove the parent.
@@ -2713,8 +2804,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
@@ -3399,7 +3488,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
{
@@ -3477,15 +3571,17 @@ 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;
@@ -3512,7 +3608,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);
}
@@ -3586,29 +3683,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,
@@ -3647,15 +3728,14 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false)
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.
@@ -3669,20 +3749,20 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false)
}
}
else // Otherwise, the client did not find the client to server scene handle
- 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
- // server - client scene handle mismatch has occurred. It also seemed pertinent to make the message replicate to
- // the server-side too.
- NetworkLog.LogWarningServer($"[Client-{NetworkManagerOwner.LocalClientId}][{name}] Server - " +
- $"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" +
- $"has no associated server side (network) scene handle!");
- }
+ 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
+ // server - client scene handle mismatch has occurred. It also seemed pertinent to make the message replicate to
+ // the server-side too.
+ NetworkLog.LogWarningServer($"[Client-{NetworkManagerOwner.LocalClientId}][{name}] Server - " +
+ $"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" +
+ $"has no associated server side (network) scene handle!");
+ }
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..df08bee61f 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
{
@@ -158,5 +160,11 @@ private static bool TryGetNetworkObjectName([NotNull] NetworkManager networkMana
return true;
}
+ [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..dba918a679 100644
--- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs
+++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs
@@ -360,13 +360,13 @@ 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)
- {
- if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ if (!networkManager.DistributedAuthorityMode && networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode)
{
- NetworkLog.LogWarning("Server is changing client synchronization mode after clients have been synchronized! It is recommended to do this before clients are connected!");
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
+ {
+ NetworkLog.LogWarning("Server is changing client synchronization mode after clients have been synchronized! It is recommended to do this before clients are connected!");
+ }
}
- }
// For additive client synchronization, we take into consideration scenes
// already loaded.
diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs
index 6ab98a8e88..f1093eb8b4 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);
@@ -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;
diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs
index 744a723c22..0b65c08ed9 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
@@ -1115,7 +1104,7 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager)
// information during synchronization.
if (serializedObject.HasGhost)
{
- if (networkManager.SpawnManager.GhostSpawnManager.ShouldDeferGhostSceneObject(serializedObject, InternalBuffer))
+ if (networkManager.SpawnManager.GhostSpawnManager.ShouldDeferGhostSceneObject(serializedObject, m_InternalBuffer))
{
continue;
}
@@ -1127,21 +1116,23 @@ 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;
+ }
+
if (EnableSerializationLogs)
{
- var noStop = InternalBuffer.Position;
+ var noStop = m_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 +1163,7 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager)
}
finally
{
- InternalBuffer.Dispose();
+ m_InternalBuffer.Dispose();
m_HasInternalBuffer = false;
}
}
@@ -1181,7 +1172,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 +1191,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();
@@ -1242,7 +1233,7 @@ private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer)
writer.WriteValueSafe(sceneManager.ObjectsMigratedIntoNewScene.Count);
foreach (var sceneHandleObjects in sceneManager.ObjectsMigratedIntoNewScene)
{
- if (!sceneManager.ObjectsMigratedIntoNewScene[sceneHandleObjects.Key].ContainsKey(ownerId))
+ if (!sceneHandleObjects.Value.ContainsKey(ownerId))
{
throw new Exception($"Trying to send object scene migration for Client-{ownerId} but the client has no entries to send!");
}
@@ -1266,40 +1257,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 +1297,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 +1306,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 +1339,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 +1351,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 +1369,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..99826f39e3 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,15 +614,16 @@ 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)
@@ -610,38 +633,44 @@ public unsafe void ReadValueSafe(out string s, bool oneByteChars = false)
}
#endif
- if (!TryBeginReadInternal(SizeOfLengthField()))
+ if (!TryBeginReadInternal(sizeof(int)))
{
throw new OverflowException("Reading past the end of the buffer");
}
ReadLength(out int length);
- if (!TryBeginReadInternal(length * (oneByteChars ? 1 : sizeof(char))))
+ // Validate the string's byte count based on the character count.
+ ValidateStringByteCount(length, oneByteChars);
+
+ // 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);
+
+ // 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 34cf77d959..d4368a0b95 100644
--- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs
@@ -345,10 +345,10 @@ internal void HandleNetworkPrefabDestroy(NetworkObject networkObjectInstance)
}
}
else // Otherwise the NetworkObject is the source NetworkPrefab
- if (m_PrefabAssetToPrefabHandler.TryGetValue(networkObjectInstanceHash, out var prefabInstanceHandler))
- {
- prefabInstanceHandler.Destroy(networkObjectInstance);
- }
+ 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..c000a91627 100644
--- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
@@ -1151,48 +1151,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 && sceneObject)
{
- 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,13 +1189,13 @@ 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))
{
@@ -1319,9 +1284,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 +1301,7 @@ internal bool SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong
return false;
}
- if (networkId == default)
+ if (networkId == 0)
{
if (NetworkManager.LogLevel <= LogLevel.Error)
{
@@ -1350,82 +1316,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;
-
- // When spawned, previous owner is always the first assigned owner
- networkObject.PreviousOwnerId = ownerClientId;
+ networkObject.SetupOnSpawn(networkId, playerObject, ownerClientId, destroyWithScene);
- // 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 +1392,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 +1418,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,
@@ -1578,8 +1485,7 @@ internal void ServerResetShutdownStateForSceneObjects()
{
continue;
}
- sobj.IsSpawned = false;
- sobj.DestroyWithScene = false;
+ sobj.ResetOnDespawn();
}
}
@@ -1853,10 +1759,10 @@ internal void OnDespawnObject([NotNull] NetworkObject networkObject, bool destro
}
}
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");
- }
+ 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..461d85a757 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs
@@ -247,11 +247,11 @@ private bool ValidateAllPlayerObjects()
success = false;
}
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;
- }
+ 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;
+ }
}
return success;
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/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/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..43f6eac7de 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs
@@ -489,10 +489,10 @@ private bool Object1IsNotVisibileToClient()
}
}
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!");
- }
+ 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!");
+ }
}
return m_ErrorLog.Length == 0;
}
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs
index e5fb5a4ba9..5705d21942 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs
@@ -419,12 +419,12 @@ private bool ShouldSyncAxis(bool first, bool second, bool lastValue)
return false;
}
else
- if (!first && !second)
- {
- // If both are disabled, then make the
- // last one enabled.
- return true;
- }
+ if (!first && !second)
+ {
+ // If both are disabled, then make the
+ // last one enabled.
+ return true;
+ }
}
return Random.Range(start, 100) >= 50 ? false : true;
}
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..1a7d768831 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
using System.Collections;
using System.Collections.Generic;
-using System.Text;
using NUnit.Framework;
using Unity.Netcode.Components;
using Unity.Netcode.TestHelpers.Runtime;
@@ -10,487 +9,227 @@
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();
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