New "OnReadySever" CallBack on NetworkBehaviours #215
Replies: 6 comments 1 reply
-
OnSpawnServer is invoked every time a client gains visibility. Not all clients gain visibility at once, and that's not how the observer system works either. It can iterate a few ways but it always checks one client, then the next, ect. At best, to pull this off every rebuild would need to track each NOB which had clients added, and then invoke on those nobs afterwards. And if you wanted it to only fire once I'd have to track which clients have gained visibility of this object at least once. This also means cleaning up client references in areas when they disconnect. I feel like there's a better way to go about this. Perhaps if I knew which scenario specifically you needed this for I could better advise. |
Beta Was this translation helpful? Give feedback.
-
I can give an example but it may be a long one haha.
We solved this by adding a OnReadyServer Method on our Network Behaviour. Its makeshift and kinda janky but works for now. On Spawn Server we grab the current number of connected clients and iterator of clients that have had the spawn message sent.
We have thought of other ways to solve this with SyncVars, and other methods/events, but It just seemed like built in callback designed specifically for this would be beneficial for all. |
Beta Was this translation helpful? Give feedback.
-
One Good Point in Argument for OnReadyServer is also the same reason Unity has Awake, and Start. All Awakes on All Monobeahviours will be called before ANY Start methods are called for SceneObjects. This allows you to reference any monobehavior in the Scene and get the correct initialized values you set in awake on those scripts. If we use OnStartServer to Set Intial SyncVars and Values we cannot do any logic that relies on another NetworkBehaviours Initialized values in OnStartServer because of Execution Order. OnReadyServer would act as what Start is for Unity in SceneObjects. |
Beta Was this translation helpful? Give feedback.
-
I think I see a goods entry point in fishnet that achieves this mission! Hear me out! GoalSceneNetworkObjects:
Spawned NetworkObjects:
Possible Invoke PointsSceneNetworkObjects: See New Code comment inside of code block!** private void SetupSceneObjects(Scene s)
{
ListCache<NetworkObject> nobs;
SceneFN.GetSceneNetworkObjects(s, false, out nobs);
bool isHost = base.NetworkManager.IsHost;
for (int i = 0; i < nobs.Written; i++)
{
NetworkObject nob = nobs.Collection[i];
//Only setup if a scene object and not initialzied.
if (nob.IsNetworked && nob.IsSceneObject && nob.IsDeinitializing)
{
base.UpdateNetworkBehaviours(nob, true);
base.AddToSceneObjects(nob);
/* If was active in the editor (before hitting play), or currently active
* then PreInitialize without synchronizing to clients. There is no reason
* to synchronize to clients because the scene just loaded on server,
* which means clients are not yet in the scene. */
if (nob.ActiveDuringEdit || nob.gameObject.activeInHierarchy)
{
//If not host then object doesn't need to be spawned until a client joins.
if (!isHost)
SetupWithoutSynchronization(nob);
//Otherwise spawn object so observers update for clientHost.
else
SpawnWithoutChecks(nob);
}
}
}
//NewCode!!!!
// This will call OnReadyServer, after all scene objects have had their initialized callbacks invoked. Garunteeing that all scene objects will have their Initialized values set in OnStartServer available in this callback.
InvokeOnReadyServer(); <== //All Scene Objects have been Initialized, meaning all SceneNetoworkObjects have invoked OnStartServer, All Initial Observers would have been built at this point for these objects.
//End of New Code!!!
ListCaches.StoreCache(nobs);
} SpawnedNetworkObjects: See New Code comment inside of code block!** private void SpawnWithoutChecks(NetworkObject networkObject, NetworkConnection ownerConnection = null)
{
/* Setup locally without sending to clients.
* When observers are built for the network object
* during initialization spawn messages will
* be sent. */
networkObject.SetIsNetworked(true);
_spawnCache.AddValue(networkObject);
SetupWithoutSynchronization(networkObject, ownerConnection);
foreach (NetworkObject item in networkObject.ChildNetworkObjects)
{
/* Only spawn recursively if the nob state is unset.
* Unset indicates that the nob has not been */
if (item.gameObject.activeInHierarchy || item.State == NetworkObjectState.Spawned)
SpawnWithoutChecks(item, ownerConnection);
}
/* Copy to a new cache then reset _spawnCache
* just incase rebuilding observers would lead to
* more additions into _spawnCache. EG: rebuilding
* may result in additional objects being spawned
* for clients and if _spawnCache were not reset
* the same objects would be rebuilt again. This likely
* would not affect anything other than perf but who
* wants that. */
ListCache<NetworkObject> spawnCacheCopy = ListCaches.GetNetworkObjectCache();
spawnCacheCopy.AddValues(_spawnCache);
_spawnCache.Reset();
//Also rebuild observers for the object so it spawns for others.
RebuildObservers(spawnCacheCopy);
//NewCode!!!
// This Will Invoke OnReadyServer Callback on each Spawned Network Object if it is not a scene object, SceneObject Initialization will handle OnReadyServer for SceneNetworkObjects.
if(!networkObject.IsSceneObject)
{
InvokeOnReadyServer(); // <====== OnStartServer() has already been called, initial observers are built for this object just above, called only once on initial spawn.
}
//End of NewCode!!!
/* If also client then we need to make sure the object renderers have correct visibility.
* Set visibility based on if the observers contains the clientHost connection. */
if (NetworkManager.IsClient)
{
int count = spawnCacheCopy.Written;
List<NetworkObject> collection = spawnCacheCopy.Collection;
for (int i = 0; i < count; i++)
collection[i].SetRenderersVisible(networkObject.Observers.Contains(NetworkManager.ClientManager.Connection));
}
ListCaches.StoreCache(spawnCacheCopy);
} |
Beta Was this translation helpful? Give feedback.
-
Final Thoughts:Because of the diverse way that NetworkObjects build observers depending on any conditions you have set, its really up to the User on what the definition of "Ready" could mean. It could mean after every client observes this game object at the same time, like I was initially wanting but it could be defined for different types of situations for any type of game. If you want an object to wait for a certain condition to met before firing certain ObserverRpcs or TargetRpcs the User should define that for each individual object catered for their game. ConclusionOnReadyServer is a dumb idea lol |
Beta Was this translation helpful? Give feedback.
-
Closing! |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I understand the reasons on why adding another callback needs to be chosen carefully, and that it can add unneeded performance costs, if not utilized often.
So this is my attempt to justify why a OnReadyServer Callback can streamline, and help new users and even veteran users have a clear area where they can call any RPC here and it will work for already connected clients, but different from OnSpawnServer, as that fires with every client that spawns the Object.
In some online games there will be situation where you will be loading scenes, and many clients will already be connected to the server, in some cases the only time a client is added is in the initial lobby scene. All scenes after this will have the set number of clients you expect to be there already connected.
OnStartServer you will want to initialize some SyncVars for that object. You cannot call any logic here that will send RPCs to Clients because the Observer System is not built for the clients.
This becomes a problem if during the server setup I want actions to happen on the client side, that I only want to happen one time emphasis on the once. If I load clients into a new scene and unload the scene with the object on the client, and come back to that scene the OnSpawnServer callbacks will fire again and if I set any ObserverRpcs to Buffer Last they will also be triggered here which is not behaviour wanted.
I feel like it would be intuitive to have a place in fishnet where, when the object initializes on the server it will only fire one time unless server unloads the scene and reloads, and you can send ObserverRpcs at this time to already connected clients. If Clients unload this object on their end and come back to this scene the OnReadyServer Callback will not fire again, and ObserverRpcs will buffer last will not fire again.
You can also give a place in guides that this is a safe place to call ObserverRpcs for clients already connected to server, which there is no clear area currently described in guides that show you where this is possible. Everyone has to come up with their own implementation to do this.
So to get rid of this ambiguity, make the OnReadyServer.
OnReadyServer Overview:
The end!
Beta Was this translation helpful? Give feedback.
All reactions