Author Topic: Host Change doesn't go well  (Read 7221 times)

WolfTechGames

  • Newbie
  • *
  • Thank You
  • -Given: 6
  • -Receive: 4
  • Posts: 35
    • View Profile
Host Change doesn't go well
« on: October 22, 2016, 10:39:22 PM »
Hello, so in our game we have blobs that can be eaten in order to grow. Now we are trying to work on a dedicated server using the external executable of tnet as we always used the internal way which always meant that when the host left the server shut down. Now in this new method when the host leaves its passed onto someone else. We have added the OnHostChange callbacks but it seems to break some stuff. When the host leaves, the role is passed on but when the other host comes back some or all of the blobs are missing from before. The new host can still see all of them but the client can't. Here are screenshots to show that:

Before Leave:


Rejoin:


Here is the code parts:

  1. if (go.Contains("Blob"))
  2.                     TNManager.Instantiate(1, "CreateBlob", go, true, hit.point, transform.rotation, go, objID, true, 1);
  3.                 else
  4.                     TNManager.Instantiate(1, "CreateObject", go, true, hit.point, transform.rotation, go, objID);
  5.  
  6. [RCC]
  7.     static GameObject CreateBlob(GameObject prefab, Vector3 pos, Quaternion rot, string itemPath, int objID, bool randValue, float setPoints)
  8.     {
  9.         // Instantiate the prefab
  10.         GameObject go = prefab.Instantiate();
  11.  
  12.         // Set the position and rotation based on the passed values
  13.         Transform t = go.transform;
  14.         t.position = pos;
  15.         t.rotation = rot;
  16.  
  17.         // Create Blob Script
  18.         Blob blob = go.AddComponent<Blob>();
  19.  
  20.         if(randValue)
  21.         {
  22.             float randPoints = Random.Range(0.1f, 0.3f);
  23.             blob.blobPoints = randPoints;
  24.         }
  25.         else
  26.         {
  27.             blob.blobPoints = setPoints;
  28.         }
  29.  
  30.         // Set Name
  31.         go.name = go.name + objID;
  32.  
  33.         // Add Spawned Item Class
  34.         SpawnedItem sitem = go.AddComponent<SpawnedItem>();
  35.         sitem.itemPath = itemPath;
  36.  
  37.         // Add Object To List
  38.         if(!WorldSpawner.spawnedObjects.ContainsKey(itemPath))
  39.         {
  40.             WorldSpawner.spawnedObjects.Add(itemPath, new List<GameObject>());
  41.         }
  42.  
  43.         WorldSpawner.spawnedObjects[itemPath].Add(go);
  44.  
  45.         return go;
  46.     }
  47.  
  48.     [RCC]
  49.     static GameObject CreateObject(GameObject prefab, Vector3 pos, Quaternion rot, string itemPath, int objID)
  50.     {
  51.         // Instantiate the prefab
  52.         GameObject go = prefab.Instantiate();
  53.  
  54.         // Set the position and rotation based on the passed values
  55.         Transform t = go.transform;
  56.         t.position = pos;
  57.         t.rotation = rot;
  58.  
  59.         // Set Name
  60.         go.name = go.name + objID;
  61.  
  62.         // Add Spawned Item Class
  63.         SpawnedItem sitem = go.AddComponent<SpawnedItem>();
  64.         sitem.itemPath = itemPath;
  65.  
  66.         // Add Object To List
  67.         if (!WorldSpawner.spawnedObjects.ContainsKey(itemPath))
  68.         {
  69.             WorldSpawner.spawnedObjects.Add(itemPath, new List<GameObject>());
  70.         }
  71.  
  72.         // Add Object To List
  73.         WorldSpawner.spawnedObjects[itemPath].Add(go);
  74.  
  75.         return go;
  76.     }
  77.  

That's for spawning and here is the code for host change:

  1. void initSpawners()
  2.     {
  3.         if (!TNManager.isHosting)
  4.             return;
  5.  
  6.         foreach(spawnableItem spawnItm in GlobalSettings.currentOptions.mapSpawns)
  7.         {
  8.              StartCoroutine(spawnObjects(spawnItm));
  9.         }
  10.  
  11.         if (gm != null && gm.gamemodeSpawns.Count > 0)
  12.         {
  13.             foreach (spawnableItem spawnItm in gm.gamemodeSpawns)
  14.             {
  15.                 spawnItm.maxSpawn *= GlobalSettings.currentOptions.gamemodeMultiplier;
  16.                 StartCoroutine(spawnObjects(spawnItm));
  17.             }
  18.         }
  19.     }
  20.  
  21. // Change Host Callback
  22.     void hostChanged(Channel ch)
  23.     {
  24.         if(TNManager.isHosting)
  25.         {
  26.             initSpawners();
  27.         }
  28.     }
  29.  

cmifwdll

  • Global Moderator
  • Sr. Member
  • *****
  • Thank You
  • -Given: 0
  • -Receive: 149
  • Posts: 285
  • TNet Alchemist
    • View Profile
Re: Host Change doesn't go well
« Reply #1 on: October 23, 2016, 11:04:24 PM »
It's hard to tell exactly what's going on without seeing all of your code. Why exactly are you calling initSpawners() on every host change?
Also:
  1. if (go.Contains("Blob"))
  2.     TNManager.Instantiate(1, "CreateBlob", go, true, hit.point, transform.rotation, go, objID, true, 1);
  3. else
  4.     TNManager.Instantiate(1, "CreateObject", go, true, hit.point, transform.rotation, go, objID);
  5.  
This implies a GO already exists before you instantiate it through TNet, why is that?

Typically you don't need to do anything special on host change, TNet will automatically take care of everything (transferring ownership of objects and privileges). If you have some code that you need your host to run then simply put
  1. if (!TNManager.isHosting) return;
  2.  
at the top of the function and call it like normal. Doesn't have to be done on host change, and doesn't have to be ran more than once unless you need it to.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Host Change doesn't go well
« Reply #2 on: October 24, 2016, 07:10:58 AM »
I think "go" in his case is a string. A bit confusing, but would make sense given that the RCC also accepts a string there.

As cmifwdll pointed out, there is no reason to listen for the host change notification. Also it's generally better to have the object's owner do the updates rather than the host.
  1. if (!tno.isMine) return;

WolfTechGames

  • Newbie
  • *
  • Thank You
  • -Given: 6
  • -Receive: 4
  • Posts: 35
    • View Profile
Re: Host Change doesn't go well
« Reply #3 on: October 24, 2016, 02:33:38 PM »
I think "go" in his case is a string. A bit confusing, but would make sense given that the RCC also accepts a string there.

As cmifwdll pointed out, there is no reason to listen for the host change notification. Also it's generally better to have the object's owner do the updates rather than the host.
  1. if (!tno.isMine) return;

Yes go is a string, sorry for the confusion. Its the resource path. Thats actually a good idea, never considered that. The reason I was doing by host is because the host is the one who is controlling the spawning of items, if I use tno.ismine I would have to change the owner to the host on host changes. Do the rccs transfer when the host changes though?

cmifwdll

  • Global Moderator
  • Sr. Member
  • *****
  • Thank You
  • -Given: 0
  • -Receive: 149
  • Posts: 285
  • TNet Alchemist
    • View Profile
Re: Host Change doesn't go well
« Reply #4 on: October 24, 2016, 05:31:39 PM »
In your case I think it'd be best keeping it so the host spawns and updates the objects. Trying to sort out X clients all running their own spawn logic is needlessly complicated, so just keep it all on the host :)

WolfTechGames

  • Newbie
  • *
  • Thank You
  • -Given: 6
  • -Receive: 4
  • Posts: 35
    • View Profile
Re: Host Change doesn't go well
« Reply #5 on: October 24, 2016, 10:03:43 PM »
In your case I think it'd be best keeping it so the host spawns and updates the objects. Trying to sort out X clients all running their own spawn logic is needlessly complicated, so just keep it all on the host :)

Ya that's how it's managed right now, only host manages that. I got rid of the host change thing though, I found a better way of doing it. Just haven't tested it yet, will reply back if all is good.

WolfTechGames

  • Newbie
  • *
  • Thank You
  • -Given: 6
  • -Receive: 4
  • Posts: 35
    • View Profile
Re: Host Change doesn't go well
« Reply #6 on: October 25, 2016, 08:16:43 PM »
Nope no luck, I tried it again but many of the objects are gone.

Here is a image:

So the view on the bottom was the first one on the server, so it was the host. Then I connected with the client, after that I disconnected with the host. When I came back many of the blobs were missing as you can see by the bottom view.

  1. // Spawn Timer
  2.     IEnumerator spawnObjects(spawnableItem itemToSpawn)
  3.     {
  4.         // Make Sure Is Host
  5.         if (TNManager.isHosting)
  6.         {
  7.             while (GlobalSettings.currentOptions == null) yield return null;
  8.  
  9.             // Spawn Objects
  10.             if(spawnedObjects[itemToSpawn.objectPath].Count <  itemToSpawn.maxSpawn)
  11.             {
  12.                 string go = itemToSpawn.objectPath;
  13.                 Vector3 spawnPointY = new Vector3(Random.Range(-(worldSize.x/2), (worldSize.x/2)), worldSize.y, Random.Range(-(worldSize.z/2), (worldSize.z/2)));
  14.                 RaycastHit hit = new RaycastHit();
  15.  
  16.                 if (Physics.Raycast(spawnPointY, Vector3.down, out hit))
  17.                 {
  18.                     int objID = Random.Range(1, 99999);
  19.                     if (go.Contains("Blob"))
  20.                         TNManager.Instantiate(1, "CreateBlob", go, true, hit.point, transform.rotation, go, objID, true, 1);
  21.                     else
  22.                         TNManager.Instantiate(1, "CreateObject", go, true, hit.point, transform.rotation, go, objID);
  23.                 }
  24.             }
  25.         }
  26.  
  27.         // Timer
  28.         yield return new WaitForSeconds(itemToSpawn.spawnTimerInSeconds);
  29.  
  30.         // Restart
  31.         StartCoroutine(spawnObjects(itemToSpawn));
  32.     }
  33.  

No changes to the RCC functions.

WolfTechGames

  • Newbie
  • *
  • Thank You
  • -Given: 6
  • -Receive: 4
  • Posts: 35
    • View Profile
Re: Host Change doesn't go well
« Reply #7 on: October 27, 2016, 03:55:41 PM »
Forgot to mention that I got rid of the host change callbacks. I just made it so the Coroutine is ran on everyone but only processed if host.

cmifwdll

  • Global Moderator
  • Sr. Member
  • *****
  • Thank You
  • -Given: 0
  • -Receive: 149
  • Posts: 285
  • TNet Alchemist
    • View Profile
Re: Host Change doesn't go well
« Reply #8 on: October 28, 2016, 12:12:03 AM »
I'm surprised the coroutine isn't overflowing the stack from infinite recursion. This *could* be your problem, but not very likely.

I don't have TNet3 so I can't check the params you're passing to TNManager.Instantiate, but make sure the objects are being marked as persistent.
There could be a bug with channels but it's doubtful. Place a Debug.Log n your object's OnDestroy function and examine the call stack.

To be clear (and to summarize for when Aren swoops in) this is what's happening:
1. Two players join
2. The Host begins instantiating network objects from a coroutine
3. The Host leaves
4. Some time later, the player that was previously hosting rejoins
5. The rejoining player does not see all of the network objects that the other player sees.

What happens if you completely restart the game before rejoining?
Also, perhaps you shouldn't instantiate objects in the coroutine? There could be a race condition during the disconnect process. Actually, a modification could solve that:
  1. // Spawn Timer
  2. IEnumerator spawnObjects(spawnableItem itemToSpawn)
  3. {
  4.         int lastCount;
  5.         // Make Sure Is Host
  6.         while ((TNManager.isConnected) && (TNManager.isInChannel) && (TNManager.isHosting))
  7.         {
  8.                 while (GlobalSettings.currentOptions == null) yield return null;
  9.                
  10.                 // Spawn Objects
  11.                 if ((spawnedObjects[itemToSpawn.objectPath].Count <  itemToSpawn.maxSpawn) && (spawnedObjects[itemToSpawn.objectPath].Count != lastCount))
  12.                 {
  13.                         lastCount = spawnedObjects[itemToSpawn.objectPath].Count;
  14.                         string go = itemToSpawn.objectPath;
  15.                         Vector3 spawnPointY = new Vector3(Random.Range(-(worldSize.x/2), (worldSize.x/2)), worldSize.y, Random.Range(-(worldSize.z/2), (worldSize.z/2)));
  16.                         RaycastHit hit = new RaycastHit();
  17.  
  18.                         if (Physics.Raycast(spawnPointY, Vector3.down, out hit))
  19.                         {
  20.                                 int objID = Random.Range(1, 99999);
  21.                                 if (go.Contains("Blob"))
  22.                                         TNManager.Instantiate(1, "CreateBlob", go, true, hit.point, transform.rotation, go, objID, true, 1);
  23.                                 else
  24.                                         TNManager.Instantiate(1, "CreateObject", go, true, hit.point, transform.rotation, go, objID);
  25.                         }
  26.                 }
  27.                 // Timer
  28.                 yield return new WaitForSeconds(itemToSpawn.spawnTimerInSeconds);
  29.         }
  30. }
  31.  

The above should only be called once per join per itemToSpawn. The coroutine will stop running if the player is not the host, is not in a channel, OR is not connected.
Additionally, I read somewhere that local variables are persistent throughout the coroutine's lifetime, so I'm assuming int lastCount will function properly.
This should eliminate race conditions and attempts to ensure objects are given enough time to instantiate fully.

Even with all this I don't know what exactly is going wrong. For player B to see the object means it's passed through the server which means it's been saved on the channel. Packets are kept in a queue and processed in order. If a player disconnects the queue is cleared. So when the Instantiate packet comes in it means the player currently exists on the server so it'll properly be assigned an owner. When the player disconnects any persistent object owned by that player is transferred to player[0] (this is done after the disconnecting player is removed from the list, so 0 would be player B in this case). Then the SetHost packet is sent out. Then the ResponsePlayerLeft packet is sent out. In this, every TNObject on every client that's owned by the disconnected player sets its owner to TNManager.hostID (which was set in response to the SetHost packet that preceded this one).
When a player joins, the channel's list of objects is iterated over and each object is created on the connecting client. The only way player B can see an object and player A can't is if player B ignored (or fails to execute) a networked Destroy call or player A ignored (or fails to execute) a networked Instantiate call.

Other than that, the only possible way I can see this happening is if the objects aren't being marked as persistent. Again, check your TNManager.Instantiate call to verify that the persistent flag is true.

WolfTechGames

  • Newbie
  • *
  • Thank You
  • -Given: 6
  • -Receive: 4
  • Posts: 35
    • View Profile
Re: Host Change doesn't go well
« Reply #9 on: October 30, 2016, 10:10:54 AM »
Yes that is exactly what is happening. I will give the code a shot, silly me I never thought of doing it that way. One second I will respond with the result.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Host Change doesn't go well
« Reply #10 on: October 30, 2016, 01:08:14 PM »
You really should go up to TNet 3, cmifwdll. It's just... better. :)

WolfTechGames

  • Newbie
  • *
  • Thank You
  • -Given: 6
  • -Receive: 4
  • Posts: 35
    • View Profile
Re: Host Change doesn't go well
« Reply #11 on: October 30, 2016, 01:24:18 PM »
Ya TNet 3 is better than 2. I had 2 for quite some time and then when I went to 3 it was a lot smoother. I noticed a lot of new features, you can check Arens post on patch notes if you are interested to see what has been changed since TNet 2 to TNet 3 but there is a lot.

WolfTechGames

  • Newbie
  • *
  • Thank You
  • -Given: 6
  • -Receive: 4
  • Posts: 35
    • View Profile
Re: Host Change doesn't go well
« Reply #12 on: November 01, 2016, 04:12:44 PM »
I'm surprised the coroutine isn't overflowing the stack from infinite recursion. This *could* be your problem, but not very likely.

I don't have TNet3 so I can't check the params you're passing to TNManager.Instantiate, but make sure the objects are being marked as persistent.
There could be a bug with channels but it's doubtful. Place a Debug.Log n your object's OnDestroy function and examine the call stack.

To be clear (and to summarize for when Aren swoops in) this is what's happening:
1. Two players join
2. The Host begins instantiating network objects from a coroutine
3. The Host leaves
4. Some time later, the player that was previously hosting rejoins
5. The rejoining player does not see all of the network objects that the other player sees.

What happens if you completely restart the game before rejoining?
Also, perhaps you shouldn't instantiate objects in the coroutine? There could be a race condition during the disconnect process. Actually, a modification could solve that:
  1. // Spawn Timer
  2. IEnumerator spawnObjects(spawnableItem itemToSpawn)
  3. {
  4.         int lastCount;
  5.         // Make Sure Is Host
  6.         while ((TNManager.isConnected) && (TNManager.isInChannel) && (TNManager.isHosting))
  7.         {
  8.                 while (GlobalSettings.currentOptions == null) yield return null;
  9.                
  10.                 // Spawn Objects
  11.                 if ((spawnedObjects[itemToSpawn.objectPath].Count <  itemToSpawn.maxSpawn) && (spawnedObjects[itemToSpawn.objectPath].Count != lastCount))
  12.                 {
  13.                         lastCount = spawnedObjects[itemToSpawn.objectPath].Count;
  14.                         string go = itemToSpawn.objectPath;
  15.                         Vector3 spawnPointY = new Vector3(Random.Range(-(worldSize.x/2), (worldSize.x/2)), worldSize.y, Random.Range(-(worldSize.z/2), (worldSize.z/2)));
  16.                         RaycastHit hit = new RaycastHit();
  17.  
  18.                         if (Physics.Raycast(spawnPointY, Vector3.down, out hit))
  19.                         {
  20.                                 int objID = Random.Range(1, 99999);
  21.                                 if (go.Contains("Blob"))
  22.                                         TNManager.Instantiate(1, "CreateBlob", go, true, hit.point, transform.rotation, go, objID, true, 1);
  23.                                 else
  24.                                         TNManager.Instantiate(1, "CreateObject", go, true, hit.point, transform.rotation, go, objID);
  25.                         }
  26.                 }
  27.                 // Timer
  28.                 yield return new WaitForSeconds(itemToSpawn.spawnTimerInSeconds);
  29.         }
  30. }
  31.  

The above should only be called once per join per itemToSpawn. The coroutine will stop running if the player is not the host, is not in a channel, OR is not connected.
Additionally, I read somewhere that local variables are persistent throughout the coroutine's lifetime, so I'm assuming int lastCount will function properly.
This should eliminate race conditions and attempts to ensure objects are given enough time to instantiate fully.

Even with all this I don't know what exactly is going wrong. For player B to see the object means it's passed through the server which means it's been saved on the channel. Packets are kept in a queue and processed in order. If a player disconnects the queue is cleared. So when the Instantiate packet comes in it means the player currently exists on the server so it'll properly be assigned an owner. When the player disconnects any persistent object owned by that player is transferred to player[0] (this is done after the disconnecting player is removed from the list, so 0 would be player B in this case). Then the SetHost packet is sent out. Then the ResponsePlayerLeft packet is sent out. In this, every TNObject on every client that's owned by the disconnected player sets its owner to TNManager.hostID (which was set in response to the SetHost packet that preceded this one).
When a player joins, the channel's list of objects is iterated over and each object is created on the connecting client. The only way player B can see an object and player A can't is if player B ignored (or fails to execute) a networked Destroy call or player A ignored (or fails to execute) a networked Instantiate call.

Other than that, the only possible way I can see this happening is if the objects aren't being marked as persistent. Again, check your TNManager.Instantiate call to verify that the persistent flag is true.

Nope no luck unfortunately, all the objects are missing when the guy joins back. This is really odd, its like its not saving the packets.

  1. // Spawn Timer
  2.     IEnumerator spawnObjects(spawnableItem itemToSpawn)
  3.     {
  4.         // Make Sure Is Host
  5.         while ((TNManager.isConnected) && (TNManager.isHosting))
  6.         {
  7.             while (GlobalSettings.currentOptions == null) yield return null;
  8.  
  9.             // Spawn Objects
  10.             if((spawnedObjects[itemToSpawn.objectPath].Count < itemToSpawn.maxSpawn))
  11.             {
  12.                 string go = itemToSpawn.objectPath;
  13.                 Vector3 spawnPointY = new Vector3(Random.Range(-(worldSize.x/2), (worldSize.x/2)), worldSize.y, Random.Range(-(worldSize.z/2), (worldSize.z/2)));
  14.                 RaycastHit hit = new RaycastHit();
  15.  
  16.                 if (Physics.Raycast(spawnPointY, Vector3.down, out hit))
  17.                 {
  18.                     int objID = Random.Range(1, 99999);
  19.                     if (go.Contains("Blob"))
  20.                         TNManager.Instantiate(1, "CreateBlob", go, true, hit.point, transform.rotation, go, objID, true, 1);
  21.                     else
  22.                         TNManager.Instantiate(1, "CreateObject", go, true, hit.point, transform.rotation, go, objID);
  23.                 }
  24.             }
  25.  
  26.             // Timer
  27.             yield return new WaitForSeconds(itemToSpawn.spawnTimerInSeconds);
  28.         }
  29.     }

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Host Change doesn't go well
« Reply #13 on: November 03, 2016, 08:26:45 AM »
I don't see your JoinChannel call in the code you posted. Make sure you also call it as persistent. By default it isn't.

WolfTechGames

  • Newbie
  • *
  • Thank You
  • -Given: 6
  • -Receive: 4
  • Posts: 35
    • View Profile
Re: Host Change doesn't go well
« Reply #14 on: November 04, 2016, 10:03:45 PM »
I don't see your JoinChannel call in the code you posted. Make sure you also call it as persistent. By default it isn't.

Yes this was the issue, I changed it to persistent in the join channel call and just had to change some things with some of my scripts then it worked fine. Thank you for your help! :)