|
2 | 2 | using System.Collections;
|
3 | 3 | using System.Collections.Generic;
|
4 | 4 | using System.Linq;
|
| 5 | +using System.Text; |
5 | 6 | using NUnit.Framework;
|
6 | 7 | using Unity.Netcode.Components;
|
7 | 8 | using Unity.Netcode.TestHelpers.Runtime;
|
@@ -539,5 +540,279 @@ protected override bool OnIsServerAuthoritative()
|
539 | 540 | }
|
540 | 541 | }
|
541 | 542 | }
|
| 543 | + |
| 544 | + [TestFixture(HostOrServer.DAHost, NetworkTransform.AuthorityModes.Owner)] // Validate the NetworkTransform owner authoritative mode fix using distributed authority |
| 545 | + [TestFixture(HostOrServer.Host, NetworkTransform.AuthorityModes.Server)] // Validate we have not impacted NetworkTransform server authoritative mode |
| 546 | + [TestFixture(HostOrServer.Host, NetworkTransform.AuthorityModes.Owner)] // Validate the NetworkTransform owner authoritative mode fix using client-server |
| 547 | + internal class NestedNetworkTransformTests : IntegrationTestWithApproximation |
| 548 | + { |
| 549 | + private const int k_NestedChildren = 5; |
| 550 | + protected override int NumberOfClients => 2; |
| 551 | + |
| 552 | + private GameObject m_SpawnObject; |
| 553 | + |
| 554 | + private NetworkTransform.AuthorityModes m_AuthorityMode; |
| 555 | + |
| 556 | + private StringBuilder m_ErrorLog = new StringBuilder(); |
| 557 | + |
| 558 | + private List<NetworkManager> m_NetworkManagers = new List<NetworkManager>(); |
| 559 | + private List<GameObject> m_SpawnedObjects = new List<GameObject>(); |
| 560 | + |
| 561 | + public NestedNetworkTransformTests(HostOrServer hostOrServer, NetworkTransform.AuthorityModes authorityMode) : base(hostOrServer) |
| 562 | + { |
| 563 | + m_AuthorityMode = authorityMode; |
| 564 | + } |
| 565 | + |
| 566 | + /// <summary> |
| 567 | + /// Creates a player prefab with several nested NetworkTransforms |
| 568 | + /// </summary> |
| 569 | + protected override void OnCreatePlayerPrefab() |
| 570 | + { |
| 571 | + var networkTransform = m_PlayerPrefab.AddComponent<NetworkTransform>(); |
| 572 | + networkTransform.AuthorityMode = m_AuthorityMode; |
| 573 | + var parent = m_PlayerPrefab; |
| 574 | + // Add several nested NetworkTransforms |
| 575 | + for (int i = 0; i < k_NestedChildren; i++) |
| 576 | + { |
| 577 | + var nestedChild = new GameObject(); |
| 578 | + nestedChild.transform.parent = parent.transform; |
| 579 | + var nestedNetworkTransform = nestedChild.AddComponent<NetworkTransform>(); |
| 580 | + nestedNetworkTransform.AuthorityMode = m_AuthorityMode; |
| 581 | + nestedNetworkTransform.InLocalSpace = true; |
| 582 | + parent = nestedChild; |
| 583 | + } |
| 584 | + base.OnCreatePlayerPrefab(); |
| 585 | + } |
| 586 | + |
| 587 | + private void RandomizeObjectTransformPositions(GameObject gameObject) |
| 588 | + { |
| 589 | + var networkObject = gameObject.GetComponent<NetworkObject>(); |
| 590 | + Assert.True(networkObject.ChildNetworkBehaviours.Count > 0); |
| 591 | + |
| 592 | + foreach (var networkTransform in networkObject.NetworkTransforms) |
| 593 | + { |
| 594 | + networkTransform.gameObject.transform.position = GetRandomVector3(-15.0f, 15.0f); |
| 595 | + } |
| 596 | + } |
| 597 | + |
| 598 | + /// <summary> |
| 599 | + /// Randomizes each player's position when validating distributed authority |
| 600 | + /// </summary> |
| 601 | + /// <returns></returns> |
| 602 | + private GameObject FetchLocalPlayerPrefabToSpawn() |
| 603 | + { |
| 604 | + RandomizeObjectTransformPositions(m_PlayerPrefab); |
| 605 | + return m_PlayerPrefab; |
| 606 | + } |
| 607 | + |
| 608 | + /// <summary> |
| 609 | + /// Randomizes the player position when validating client-server |
| 610 | + /// </summary> |
| 611 | + /// <param name="connectionApprovalRequest"></param> |
| 612 | + /// <param name="connectionApprovalResponse"></param> |
| 613 | + private void ConnectionApprovalHandler(NetworkManager.ConnectionApprovalRequest connectionApprovalRequest, NetworkManager.ConnectionApprovalResponse connectionApprovalResponse) |
| 614 | + { |
| 615 | + connectionApprovalResponse.Approved = true; |
| 616 | + connectionApprovalResponse.CreatePlayerObject = true; |
| 617 | + RandomizeObjectTransformPositions(m_PlayerPrefab); |
| 618 | + connectionApprovalResponse.Position = GetRandomVector3(-15.0f, 15.0f); |
| 619 | + } |
| 620 | + |
| 621 | + protected override void OnServerAndClientsCreated() |
| 622 | + { |
| 623 | + // Create a prefab to spawn with each NetworkManager as the owner |
| 624 | + m_SpawnObject = CreateNetworkObjectPrefab("SpawnObj"); |
| 625 | + var networkTransform = m_SpawnObject.AddComponent<NetworkTransform>(); |
| 626 | + networkTransform.AuthorityMode = m_AuthorityMode; |
| 627 | + var parent = m_SpawnObject; |
| 628 | + // Add several nested NetworkTransforms |
| 629 | + for (int i = 0; i < k_NestedChildren; i++) |
| 630 | + { |
| 631 | + var nestedChild = new GameObject(); |
| 632 | + nestedChild.transform.parent = parent.transform; |
| 633 | + var nestedNetworkTransform = nestedChild.AddComponent<NetworkTransform>(); |
| 634 | + nestedNetworkTransform.AuthorityMode = m_AuthorityMode; |
| 635 | + nestedNetworkTransform.InLocalSpace = true; |
| 636 | + parent = nestedChild; |
| 637 | + } |
| 638 | + |
| 639 | + if (m_DistributedAuthority) |
| 640 | + { |
| 641 | + if (!UseCMBService()) |
| 642 | + { |
| 643 | + m_ServerNetworkManager.OnFetchLocalPlayerPrefabToSpawn = FetchLocalPlayerPrefabToSpawn; |
| 644 | + } |
| 645 | + |
| 646 | + foreach (var client in m_ClientNetworkManagers) |
| 647 | + { |
| 648 | + client.OnFetchLocalPlayerPrefabToSpawn = FetchLocalPlayerPrefabToSpawn; |
| 649 | + } |
| 650 | + } |
| 651 | + else |
| 652 | + { |
| 653 | + m_ServerNetworkManager.NetworkConfig.ConnectionApproval = true; |
| 654 | + m_ServerNetworkManager.ConnectionApprovalCallback += ConnectionApprovalHandler; |
| 655 | + foreach (var client in m_ClientNetworkManagers) |
| 656 | + { |
| 657 | + client.NetworkConfig.ConnectionApproval = true; |
| 658 | + } |
| 659 | + } |
| 660 | + |
| 661 | + base.OnServerAndClientsCreated(); |
| 662 | + } |
| 663 | + |
| 664 | + /// <summary> |
| 665 | + /// Validates the transform positions of two NetworkObject instances |
| 666 | + /// </summary> |
| 667 | + /// <param name="current">the local instance (source of truth)</param> |
| 668 | + /// <param name="testing">the remote instance</param> |
| 669 | + /// <returns></returns> |
| 670 | + private bool ValidateTransforms(NetworkObject current, NetworkObject testing) |
| 671 | + { |
| 672 | + if (current.ChildNetworkBehaviours.Count == 0 || testing.ChildNetworkBehaviours.Count == 0) |
| 673 | + { |
| 674 | + return false; |
| 675 | + } |
| 676 | + |
| 677 | + for (int i = 0; i < current.NetworkTransforms.Count - 1; i++) |
| 678 | + { |
| 679 | + var transformA = current.NetworkTransforms[i].transform; |
| 680 | + var transformB = testing.NetworkTransforms[i].transform; |
| 681 | + if (!Approximately(transformA.position, transformB.position)) |
| 682 | + { |
| 683 | + m_ErrorLog.AppendLine($"TransformA Position {transformA.position} != TransformB Position {transformB.position}"); |
| 684 | + return false; |
| 685 | + } |
| 686 | + if (!Approximately(transformA.localPosition, transformB.localPosition)) |
| 687 | + { |
| 688 | + m_ErrorLog.AppendLine($"TransformA Local Position {transformA.position} != TransformB Local Position {transformB.position}"); |
| 689 | + return false; |
| 690 | + } |
| 691 | + if (transformA.parent != null) |
| 692 | + { |
| 693 | + if (current.NetworkTransforms[i].InLocalSpace != testing.NetworkTransforms[i].InLocalSpace) |
| 694 | + { |
| 695 | + m_ErrorLog.AppendLine($"NetworkTransform-{current.OwnerClientId}-{current.NetworkTransforms[i].NetworkBehaviourId} InLocalSpace ({current.NetworkTransforms[i].InLocalSpace}) is different from the remote instance version on Client-{testing.NetworkManager.LocalClientId}!"); |
| 696 | + return false; |
| 697 | + } |
| 698 | + } |
| 699 | + } |
| 700 | + return true; |
| 701 | + } |
| 702 | + |
| 703 | + /// <summary> |
| 704 | + /// Validates all player instances spawned with the correct positions including all nested NetworkTransforms |
| 705 | + /// When running in server authority mode we are validating this fix did not impact that. |
| 706 | + /// </summary> |
| 707 | + private bool AllClientInstancesSynchronized() |
| 708 | + { |
| 709 | + m_ErrorLog.Clear(); |
| 710 | + |
| 711 | + foreach (var current in m_NetworkManagers) |
| 712 | + { |
| 713 | + var currentPlayer = current.LocalClient.PlayerObject; |
| 714 | + var currentNetworkObjectId = currentPlayer.NetworkObjectId; |
| 715 | + foreach (var testing in m_NetworkManagers) |
| 716 | + { |
| 717 | + if (currentPlayer == testing.LocalClient.PlayerObject) |
| 718 | + { |
| 719 | + continue; |
| 720 | + } |
| 721 | + |
| 722 | + if (!testing.SpawnManager.SpawnedObjects.ContainsKey(currentNetworkObjectId)) |
| 723 | + { |
| 724 | + m_ErrorLog.AppendLine($"Failed to find Client-{currentPlayer.OwnerClientId}'s player instance on Client-{testing.LocalClientId}!"); |
| 725 | + return false; |
| 726 | + } |
| 727 | + |
| 728 | + var remoteInstance = testing.SpawnManager.SpawnedObjects[currentNetworkObjectId]; |
| 729 | + if (!ValidateTransforms(currentPlayer, remoteInstance)) |
| 730 | + { |
| 731 | + m_ErrorLog.AppendLine($"Failed to validate Client-{currentPlayer.OwnerClientId} against its remote instance on Client-{testing.LocalClientId}!"); |
| 732 | + return false; |
| 733 | + } |
| 734 | + } |
| 735 | + } |
| 736 | + return true; |
| 737 | + } |
| 738 | + |
| 739 | + /// <summary> |
| 740 | + /// Validates that dynamically spawning works the same. |
| 741 | + /// When running in server authority mode we are validating this fix did not impact that. |
| 742 | + /// </summary> |
| 743 | + /// <returns></returns> |
| 744 | + private bool AllSpawnedObjectsSynchronized() |
| 745 | + { |
| 746 | + m_ErrorLog.Clear(); |
| 747 | + |
| 748 | + foreach (var current in m_SpawnedObjects) |
| 749 | + { |
| 750 | + var currentNetworkObject = current.GetComponent<NetworkObject>(); |
| 751 | + var currentNetworkObjectId = currentNetworkObject.NetworkObjectId; |
| 752 | + foreach (var testing in m_NetworkManagers) |
| 753 | + { |
| 754 | + if (currentNetworkObject.OwnerClientId == testing.LocalClientId) |
| 755 | + { |
| 756 | + continue; |
| 757 | + } |
| 758 | + |
| 759 | + if (!testing.SpawnManager.SpawnedObjects.ContainsKey(currentNetworkObjectId)) |
| 760 | + { |
| 761 | + m_ErrorLog.AppendLine($"Failed to find Client-{currentNetworkObject.OwnerClientId}'s player instance on Client-{testing.LocalClientId}!"); |
| 762 | + return false; |
| 763 | + } |
| 764 | + |
| 765 | + var remoteInstance = testing.SpawnManager.SpawnedObjects[currentNetworkObjectId]; |
| 766 | + if (!ValidateTransforms(currentNetworkObject, remoteInstance)) |
| 767 | + { |
| 768 | + m_ErrorLog.AppendLine($"Failed to validate Client-{currentNetworkObject.OwnerClientId} against its remote instance on Client-{testing.LocalClientId}!"); |
| 769 | + return false; |
| 770 | + } |
| 771 | + } |
| 772 | + } |
| 773 | + return true; |
| 774 | + } |
| 775 | + |
| 776 | + /// <summary> |
| 777 | + /// Validates that spawning player and dynamically spawned prefab instances with nested NetworkTransforms |
| 778 | + /// synchronizes properly in both client-server and distributed authority when using owner authoritative mode. |
| 779 | + /// </summary> |
| 780 | + [UnityTest] |
| 781 | + public IEnumerator NestedNetworkTransformSpawnPositionTest() |
| 782 | + { |
| 783 | + if (!m_DistributedAuthority || (m_DistributedAuthority && !UseCMBService())) |
| 784 | + { |
| 785 | + m_NetworkManagers.Add(m_ServerNetworkManager); |
| 786 | + } |
| 787 | + m_NetworkManagers.AddRange(m_ClientNetworkManagers); |
| 788 | + |
| 789 | + yield return WaitForConditionOrTimeOut(AllClientInstancesSynchronized); |
| 790 | + AssertOnTimeout($"Failed to synchronize all client instances!\n{m_ErrorLog}"); |
| 791 | + |
| 792 | + foreach (var networkManager in m_NetworkManagers) |
| 793 | + { |
| 794 | + // Randomize the position |
| 795 | + RandomizeObjectTransformPositions(m_SpawnObject); |
| 796 | + |
| 797 | + // Create an instance owned by the specified networkmanager |
| 798 | + m_SpawnedObjects.Add(SpawnObject(m_SpawnObject, networkManager)); |
| 799 | + } |
| 800 | + // Randomize the position once more just to assure we are instantiating remote instances |
| 801 | + // with a completely different position |
| 802 | + RandomizeObjectTransformPositions(m_SpawnObject); |
| 803 | + yield return WaitForConditionOrTimeOut(AllSpawnedObjectsSynchronized); |
| 804 | + AssertOnTimeout($"Failed to synchronize all spawned NetworkObject instances!\n{m_ErrorLog}"); |
| 805 | + m_SpawnedObjects.Clear(); |
| 806 | + m_NetworkManagers.Clear(); |
| 807 | + } |
| 808 | + |
| 809 | + protected override IEnumerator OnTearDown() |
| 810 | + { |
| 811 | + // In case there was a failure, go ahead and clear these lists out for any pending TextFixture passes |
| 812 | + m_SpawnedObjects.Clear(); |
| 813 | + m_NetworkManagers.Clear(); |
| 814 | + return base.OnTearDown(); |
| 815 | + } |
| 816 | + } |
542 | 817 | }
|
543 | 818 | #endif
|
0 commit comments