Skip to content

feat: Allow to pass custom data on Spawn calls #3419

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from

Conversation

Extrys
Copy link

@Extrys Extrys commented Apr 23, 2025

This PR introduces support for passing custom spawn data from the server to clients during the spawn process of a NetworkObject.
This enables deterministic instantiation or contextual setup on the client without relying on RPCs or external sync steps.

Doesn't break or invalidate existing user code
the feature is fully optional and designed via an extension interface to preserve full backward compatibility.

Changelog

  • Added: INetworkCustomSpawnDataReceiver interface to allow reading custom spawn data on the client side.
  • Changed: CreateObjectMessage now includes optional CustomSpawnData handling.
  • Changed: Involved spawn methods now includes optional CustomSpawnData parameter.

Testing and Documentation

  • No tests have been added.

Deprecated API

  • No APIs were deprecated in this PR.

Backport

  • No backport required. This is a new feature for the current development version only.

Implementation example

public class SyncedPrefabInstanceHandler : NetworkBehaviour, INetworkPrefabInstanceHandler, INetworkCustomSpawnDataReceiver
{
	public GameObject basePrefab;
	public NetworkObject basePrefabN;

	public GameObject[] prefabs;

	public override void OnNetworkSpawn()
	{
		NetworkManager.AddNetworkPrefab(basePrefab);
		NetworkManager.PrefabHandler.AddHandler(basePrefab, this);
	}
	public override void OnNetworkDespawn()
	{
		NetworkManager.PrefabHandler.RemoveHandler(basePrefab);
		NetworkManager.RemoveNetworkPrefab(basePrefab);
	}

       //See how i send custom data on the spawn
	public void Spawn(int metadata)
	{
		NetworkManager.SpawnManager.InstantiateAndSpawn(basePrefabN, customSpawnData: new byte[] { (byte)metadata});
	}

	int spawnData;
        // Implementing the INetworkCustomSpawnDataReceiver, i can catch this custom data just before the instantiate
	public void OnCustomSpawnDataReceived(byte[] customSpawnData)
	{
		Debug.Log("${nameof(SyncedPrefabInstanceHandler)}: Received custom spawn data: " + customSpawnData[0]);
		spawnData = customSpawnData[0];
	}
    
        //in the instantiate i use the custom spawn data allow looking for preinstantiated instances or spawning compatible arbitrary prefabs by id
	public NetworkObject Instantiate(ulong clientId, Vector3 position, Quaternion rotation)
	{
		return Instantiate(prefabs[spawnData], position, rotation).GetComponent<NetworkObject>();
	}

	public void Destroy(NetworkObject networkObject)
	{
		GameObject.Destroy(networkObject.gameObject);
	}
}```

@Extrys Extrys requested review from NoelStephensUnity, EmandM and a team as code owners April 23, 2025 17:35
@unity-cla-assistant
Copy link

unity-cla-assistant commented Apr 23, 2025

CLA assistant check
All committers have signed the CLA.

@Extrys Extrys changed the title Release/2.3.3 feat: Allow to pass custom data on Spawn calls Apr 23, 2025
@EmandM
Copy link
Collaborator

EmandM commented Apr 23, 2025

Thank you for your contribution!

At this time, we would rather avoid adding parameters to the Spawn(), SpawnWithOwnership() and SpawnPlayerObject() methods. These are very high traffic methods and it is very easy for feature creep to happen in these places. This approach also introduces the potential for strange side effects around in scene placed Network Objects.

Could an approach like the NetworkBehaviour.OnSynchronize() method work for your use case? It might be easier if you could open an issue on this topic so we can discuss the use case more in-depth.

@Extrys
Copy link
Author

Extrys commented Apr 23, 2025

Thank you for your contribution!

At this time, we would rather avoid adding parameters to the Spawn(), SpawnWithOwnership() and SpawnPlayerObject() methods. These are very high traffic methods and it is very easy for feature creep to happen in these places. This approach also introduces the potential for strange side effects around in scene placed Network Objects.

Could an approach like the NetworkBehaviour.OnSynchronize() method work for your use case? It might be easier if you could open an issue on this topic so we can discuss the use case more in-depth.

thanks for the feedback! i will open an issue regarding my use case

there is currently a forum post on unity i made for it, which is this
https://discussions.unity.com/t/allow-passing-spawn-metadata-to-instantiate-via-prefabhandler/1630903/2

I might copiy this into an issue to see if there is a pleasing solution

here is the issue i created
#3421

Also would it help if it were a new method instead of adding a parameter to the original method?
something like InstantiateAndSpawnWithCustomData(indexOfPrefabHandler, customSpawnData)

Perhaps I could explore introducing a kind of spawn builder system where the Spawn() methods remain untouched, but external data can be attached through a chainable builder pattern. This would preserve the current API while allowing advanced usage patterns without impacting existing workflows or causing order issues

Another idea I’d like to experiment with is decoupling the INetworkPrefabInstanceHandler from prefabs entirely, so it acts more like a general-purpose "Spawner" rather than being strictly tied to prefab references

@Extrys
Copy link
Author

Extrys commented Apr 24, 2025

In order to avoid feature creeping the core spawn methods, I’d propose a change that keeps those untouched.
Just like IPrefabInstanceHandler is registered with the SpawnManager, we could introduce the INetworkCustomSpawnDataReceiver interface, which gets associated with an existing IPrefabInstanceHandler in the same way

so the api usage would be:

NetworkManager.SpawnManager.SetCustomSpawnData(myBasePrefabToPointTheHandler, customData);
NetworkManager.SpawnManager.InstantiateAndSpawn(myBasePrefabToPointTheHandler);

And to improve support for inScenePlaced objects, this customSpawnData could optionally be embedded into the internal SceneObject struct using an additional bitmask flag, instead of in the CreateObjectMessage.
This would ensure that the metadata is available regardless of whether the object was placed in the scene or instantiated dynamically, and could be routed to the appropriate INetworkCustomSpawnDataReceiver in the same way as how INetworkPrefabInstanceHandler works.

Does that sound like a better fit?
It would preserve the stability of the spawn methods, keep the architecture clean, and still enable advanced workflows such as deterministic spawning, object pooling, and external data injection, all without relying on pre-spawn RPC order guarantees, client-side re-instancing just to make OnSynchronize() viable, or maintaining unindexed pre-instantiated object pools tied to the default INetworkPrefabInstanceHandler

If this approach sounds more aligned with the direction you'd prefer, I’d be happy to proceed with the necessary changes to make it viable

@michalChrobot
Copy link
Collaborator

Hey, just a quick PR related note that you should probably rebase to target develop-2.0.0 (our development branch for NGOv2.X) instead of release/2.3.3

@Extrys Extrys changed the base branch from release/2.3.3 to develop-2.0.0 April 24, 2025 08:59
@Extrys
Copy link
Author

Extrys commented Apr 24, 2025

Hey, just a quick PR related note that you should probably rebase to target develop-2.0.0 (our development branch for NGOv2.X) instead of release/2.3.3

Thanks for letting me know! I've just rebased the PR to target develop-2.0.0 as suggested 🙌
I'd just need to know if the approach I proposed above sounds viable, so I can start working on the updated implementation

@NoelStephensUnity
Copy link
Collaborator

Hey, just a quick PR related note that you should probably rebase to target develop-2.0.0 (our development branch for NGOv2.X) instead of release/2.3.3

Thanks for letting me know! I've just rebased the PR to target develop-2.0.0 as suggested 🙌 I'd just need to know if the approach I proposed above sounds viable, so I can start working on the updated implementation

Hi @Extrys,

Thank you for submitting your proposed changes. We always love community contributions...of course we might not always have enough time to review everything. Unfortunately, the change you are proposing is most likely not going to be approved for several reasons. The primary reason is that while your PR submission appears to cover the initial spawning of the NetworkObject it does not handle synchronizing late joining clients.

Example:

  • A host starts a session.
    • Before any clients connect, it spawns 10 network prefabs using custom spawn information.
    • Since there are no connected clients the data passed in via any NetworkObject spawn method will not be sent to any client.
  • A client joins 5-10 seconds later.
    • The host begins the Synchronization Process.
      • This uses the SceneEventMessage that invokes the SceneEventData.Serialize method.
        • Within the SceneEventData.Serialize method, it runs through all of the spawned NetworkObjects and invokes NetworkObject.GetMessageSceneObject method.
          • This will serialize each NetworkObject based on the SceneObject structure populated, the NetworkVariables of each NetworkBehaviour, and any OnSynchronize custom serialization data per NetworkBehaviour.
          • This will no longer have access to the initial customSpawnData that was used on the host side to spawn the objects and you will still be confronted with the same issue.

Your implementation would only work under the condition of:

  • The host and clients are already connected in a session and no NetworkObjects were spawned.
  • The host then spawns NetworkObjects including the customSpawnData.
  • The clients would receive the customSpawnData.
  • Any late joining client would not receive the customSpawnData.

I had posted a reply to your forum post here asking a bit more about why the ID assignment and more information as to why you needed this information during the instantiation as opposed to just using OnSynchronize as I had outlined.

If you could provide that information to me I might be able to assist and/or you could submit a bug report within the editor that includes your project and post that ticket here so I could look over your project and possibly provide some guidance on how you could achieve your goals without this type of modification... in the end the modification you are proposing would require storing the customData information in each instance permanently as well as change the existing public facing API (which both will be very difficult to get alignment for this without understanding your project/approach and determining if there is no other possible way to accomplish what you are trying to accomplish using the existing API).

@Extrys
Copy link
Author

Extrys commented Apr 24, 2025

Hey, just a quick PR related note that you should probably rebase to target develop-2.0.0 (our development branch for NGOv2.X) instead of release/2.3.3

Thanks for letting me know! I've just rebased the PR to target develop-2.0.0 as suggested 🙌 I'd just need to know if the approach I proposed above sounds viable, so I can start working on the updated implementation

Hi @Extrys,

Thank you for submitting your proposed changes. We always love community contributions...of course we might not always have enough time to review everything. Unfortunately, the change you are proposing is most likely not going to be approved for several reasons. The primary reason is that while your PR submission appears to cover the initial spawning of the NetworkObject it does not handle synchronizing late joining clients.

Example:

  • A host starts a session.

    • Before any clients connect, it spawns 10 network prefabs using custom spawn information.
    • Since there are no connected clients the data passed in via any NetworkObject spawn method will not be sent to any client.
  • A client joins 5-10 seconds later.

    • The host begins the Synchronization Process.

      • This uses the SceneEventMessage that invokes the SceneEventData.Serialize method.

        • Within the SceneEventData.Serialize method, it runs through all of the spawned NetworkObjects and invokes NetworkObject.GetMessageSceneObject method.

          • This will serialize each NetworkObject based on the SceneObject structure populated, the NetworkVariables of each NetworkBehaviour, and any OnSynchronize custom serialization data per NetworkBehaviour.
          • This will no longer have access to the initial customSpawnData that was used on the host side to spawn the objects and you will still be confronted with the same issue.

Your implementation would only work under the condition of:

  • The host and clients are already connected in a session and no NetworkObjects were spawned.
  • The host then spawns NetworkObjects including the customSpawnData.
  • The clients would receive the customSpawnData.
  • Any late joining client would not receive the customSpawnData.

I had posted a reply to your forum post here asking a bit more about why the ID assignment and more information as to why you needed this information during the instantiation as opposed to just using OnSynchronize as I had outlined.

If you could provide that information to me I might be able to assist and/or you could submit a bug report within the editor that includes your project and post that ticket here so I could look over your project and possibly provide some guidance on how you could achieve your goals without this type of modification... in the end the modification you are proposing would require storing the customData information in each instance permanently as well as change the existing public facing API (which both will be very difficult to get alignment for this without understanding your project/approach and determining if there is no other possible way to accomplish what you are trying to accomplish using the existing API).

Thanks a lot for the detailed breakdown and for taking the time to investigate the PR so thoroughly. I really appreciate it.

Regarding the issue with late-joining clients:
I believe it might be possible to resolve this by moving the customSpawnData into the internal SceneObject structure itself, controlled via an additional bitmask flag. That way, the data would be serialized as part of the standard synchronization process and available even for late-joiners and scene-placed objects.
As you said, storing this SpawnData permanently on the spawned object

I'll review your reply on the forum now. If we can’t find a clean solution through the existing API, I’d be happy to explore a new version of the proposal.

one that handles:

  • Late-join synchronization
  • Scene-placed object compatibility
  • Avoids modifying the public-facing API directly

Instead, the approach would rely on internal extensions and optional hooks, keeping the default flow intact for existing users.
Something like this:

NetworkManager.SpawnManager.SetCustomSpawnData(myBasePrefabToPointTheHandler, customData);
NetworkManager.SpawnManager.InstantiateAndSpawn(myBasePrefabToPointTheHandler);

Let me know if that sounds worth exploring, and thanks again for your time and feedback! ❤️

Now, heading to the forum!

@Extrys
Copy link
Author

Extrys commented Apr 27, 2025

Closing this PR in favor of a new, much cleaner and more aligned solution.
Addresses all feedback discussed here:

  • Optional and non-breaking
  • Fully aligns with NGO patterns (Serialize/Deserialize symmetry)
  • No public API modifications
  • Late-join and scene object support

Follow-up PR: #3430

@Extrys Extrys closed this Apr 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants