From 1a43b79fc5ea6d3f2792e3c1f6bad8ea79607480 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Fri, 29 Oct 2021 00:33:32 -0400 Subject: [PATCH] feat: Team Interest Management (#2979) * feat: Team Interest Management * meta file * Added forceShown * Code Smells * code smell * simplified --- .../Components/InterestManagement/Team.meta | 8 + .../InterestManagement/Team/NetworkTeam.cs | 18 ++ .../Team/NetworkTeam.cs.meta | 11 ++ .../Team/TeamInterestManagement.cs | 178 ++++++++++++++++++ .../Team/TeamInterestManagement.cs.meta | 11 ++ 5 files changed, 226 insertions(+) create mode 100644 Assets/Mirror/Components/InterestManagement/Team.meta create mode 100644 Assets/Mirror/Components/InterestManagement/Team/NetworkTeam.cs create mode 100644 Assets/Mirror/Components/InterestManagement/Team/NetworkTeam.cs.meta create mode 100644 Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs create mode 100644 Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs.meta diff --git a/Assets/Mirror/Components/InterestManagement/Team.meta b/Assets/Mirror/Components/InterestManagement/Team.meta new file mode 100644 index 0000000000..fe40aa4985 --- /dev/null +++ b/Assets/Mirror/Components/InterestManagement/Team.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2d418e60072433b4bbebbf5f3a7de1bb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Components/InterestManagement/Team/NetworkTeam.cs b/Assets/Mirror/Components/InterestManagement/Team/NetworkTeam.cs new file mode 100644 index 0000000000..16111817f7 --- /dev/null +++ b/Assets/Mirror/Components/InterestManagement/Team/NetworkTeam.cs @@ -0,0 +1,18 @@ +// simple component that holds team information +using System; +using UnityEngine; + +namespace Mirror +{ + [DisallowMultipleComponent] + [AddComponentMenu("Network/NetworkTeam")] + [HelpURL("https://mirror-networking.gitbook.io/docs/guides/interest-management")] + public class NetworkTeam : NetworkBehaviour + { + [Tooltip("Set this to the same value on all networked objects that belong to a given team")] + public string teamId = string.Empty; + + [Tooltip("When enabled this object is visible to all clients. Typically this would be true for player objects")] + public bool forceShown; + } +} diff --git a/Assets/Mirror/Components/InterestManagement/Team/NetworkTeam.cs.meta b/Assets/Mirror/Components/InterestManagement/Team/NetworkTeam.cs.meta new file mode 100644 index 0000000000..ca75a7a659 --- /dev/null +++ b/Assets/Mirror/Components/InterestManagement/Team/NetworkTeam.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2576730625b1632468cbcbfe5e721f88 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs b/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs new file mode 100644 index 0000000000..85268bc474 --- /dev/null +++ b/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; + +namespace Mirror +{ + public class TeamInterestManagement : InterestManagement + { + readonly Dictionary> teamObjects = + new Dictionary>(); + + readonly Dictionary lastObjectTeam = + new Dictionary(); + + readonly HashSet dirtyTeams = new HashSet(); + + public override void OnSpawned(NetworkIdentity identity) + { + if (!identity.TryGetComponent(out NetworkTeam networkTeam)) + return; + + string currentTeam = networkTeam.teamId; + lastObjectTeam[identity] = currentTeam; + + // string.Empty is never a valid teamId...do not add to teamObjects collection + if (currentTeam == string.Empty) + return; + + if (!teamObjects.TryGetValue(currentTeam, out HashSet objects)) + { + objects = new HashSet(); + teamObjects.Add(currentTeam, objects); + } + + objects.Add(identity); + } + + public override void OnDestroyed(NetworkIdentity identity) + { + // Do nothing if server has shut down (or not started) + if (!NetworkServer.active) return; + + lastObjectTeam.TryGetValue(identity, out string currentTeam); + lastObjectTeam.Remove(identity); + if (currentTeam != string.Empty && teamObjects.TryGetValue(currentTeam, out HashSet objects) && objects.Remove(identity)) + RebuildTeamObservers(currentTeam); + } + + [ServerCallback] + void Update() + { + // for each spawned: + // if team changed: + // add previous to dirty + // add new to dirty + foreach (NetworkIdentity netIdentity in NetworkServer.spawned.Values) + { + // Ignore objects that don't have a NetworkTeam component + if (!netIdentity.TryGetComponent(out NetworkTeam networkTeam)) + continue; + + string newTeam = networkTeam.teamId; + lastObjectTeam.TryGetValue(netIdentity, out string currentTeam); + + // string.Empty is never a valid teamId + // Nothing to do if teamId hasn't changed + if (newTeam == string.Empty || newTeam == currentTeam) + continue; + + // Mark new/old Teams as dirty so they get rebuilt + UpdateDirtyTeams(newTeam, currentTeam); + + // This object is in a new team so observers in the prior team + // and the new team need to rebuild their respective observers lists. + UpdateTeamObjects(netIdentity, newTeam, currentTeam); + } + + // rebuild all dirty teams + foreach (string dirtyTeam in dirtyTeams) + RebuildTeamObservers(dirtyTeam); + + dirtyTeams.Clear(); + } + + void UpdateDirtyTeams(string newTeam, string currentTeam) + { + // string.Empty is never a valid teamId + if (currentTeam != string.Empty) + dirtyTeams.Add(currentTeam); + + dirtyTeams.Add(newTeam); + } + + void UpdateTeamObjects(NetworkIdentity netIdentity, string newTeam, string currentTeam) + { + // Remove this object from the hashset of the team it just left + // string.Empty is never a valid teamId + if (currentTeam != string.Empty) + teamObjects[currentTeam].Remove(netIdentity); + + // Set this to the new team this object just entered + lastObjectTeam[netIdentity] = newTeam; + + // Make sure this new team is in the dictionary + if (!teamObjects.ContainsKey(newTeam)) + teamObjects.Add(newTeam, new HashSet()); + + // Add this object to the hashset of the new team + teamObjects[newTeam].Add(netIdentity); + } + + void RebuildTeamObservers(string teamId) + { + foreach (NetworkIdentity netIdentity in teamObjects[teamId]) + if (netIdentity != null) + NetworkServer.RebuildObservers(netIdentity, false); + } + + public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnection newObserver) + { + // Do nothing if server has shut down (or not started) + if (!NetworkServer.active) return false; + + // Always observed if no NetworkTeam component + if (!identity.TryGetComponent(out NetworkTeam identityNetworkTeam)) + return true; + + // Always observed if no NetworkTeam component + if (!newObserver.identity.TryGetComponent(out NetworkTeam newObserverNetworkTeam)) + return true; + + // Observed only if teamId's match + return identityNetworkTeam.forceShown || identityNetworkTeam.teamId == newObserverNetworkTeam.teamId; + } + + public override void OnRebuildObservers(NetworkIdentity identity, HashSet newObservers, bool initialize) + { + // Do nothing if server has shut down (or not started) + if (!NetworkServer.active) return; + + // If this object doesn't have a NetworkTeam then it's visible to all clients + if (!identity.TryGetComponent(out NetworkTeam networkTeam)) + { + AddAllConnections(newObservers); + return; + } + + // If this object has NetworkTeam and forceShown == true then it's visible to all clients + if (networkTeam.forceShown) + { + AddAllConnections(newObservers); + return; + } + + // string.Empty is never a valid teamId + if (networkTeam.teamId == string.Empty) + return; + + // Abort if this team hasn't been created yet by OnSpawned or UpdateTeamObjects + if (!teamObjects.TryGetValue(networkTeam.teamId, out HashSet objects)) + return; + + // Add everything in the hashset for this object's current team + foreach (NetworkIdentity networkIdentity in objects) + if (networkIdentity != null && networkIdentity.connectionToClient != null) + newObservers.Add(networkIdentity.connectionToClient); + } + + void AddAllConnections(HashSet newObservers) + { + foreach (NetworkConnectionToClient conn in NetworkServer.connections.Values) + { + // authenticated and joined world with a player? + if (conn != null && conn.isAuthenticated && conn.identity != null) + newObservers.Add(conn); + } + } + } +} diff --git a/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs.meta b/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs.meta new file mode 100644 index 0000000000..6e8748a98d --- /dev/null +++ b/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dceb9a7085758fd4590419ff5b14b636 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: + assetBundleName: + assetBundleVariant: