-
Notifications
You must be signed in to change notification settings - Fork 450
feat: Instantiation payload support for INetworkPrefabInstanceHandler #3430
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
base: develop-2.0.0
Are you sure you want to change the base?
Conversation
…efabInstanceHandler.Instantiate() documentation
I just posted a video demonstrating how the system works:
In the video, I spawn The
2025-04-27.21-45-44.mp4By implementing a custom Here is the core implementation: public class TestHandlerDeterministicLink : INetworkPrefabInstanceHandler, INetworkInstantiationPayloadSynchronizer
{
public Dictionary<int, DeterministicIDHolder> deterministicSpawns = new Dictionary<int, DeterministicIDHolder>();
public int customSpawnID = 0;
void INetworkInstantiationPayloadSynchronizer.OnSynchronize<T>(ref BufferSerializer<T> serializer) => serializer.SerializeValue(ref customSpawnID);
public NetworkObject Instantiate(ulong clientId, Vector3 position, Quaternion rotation)
{
var obj = deterministicSpawns[customSpawnID];
TMP_Text text = obj.GetComponent<TMP_Text>();
text.SetText(text.text + "*");
return obj.GetComponent<NetworkObject>();
}
public void Destroy(NetworkObject networkObject) => GameObject.Destroy(networkObject.gameObject);
int nextDeterministicId = 0;
public void InstantiateLocally(GameObject linkablePrefab)
{
var spawned = GameObject.Instantiate(linkablePrefab);
spawned.transform.position = UnityEngine.Random.insideUnitCircle * 0.01f;
var text = spawned.GetComponent<TMP_Text>();
text.SetText(nextDeterministicId.ToString() + "&" + text.text);
var deterministicIdHolder = spawned.GetComponent<DeterministicIDHolder>();
deterministicSpawns[nextDeterministicId] = deterministicIdHolder;
deterministicIdHolder.SetID(nextDeterministicId);
nextDeterministicId++;
}
} Warning While this system enables advanced workflows, |
This would actually save us a lot of trouble. Right now when after we spawn stuff we have to run like two or three RPCs just to finish setting up objects properly. I wish you luck on get it merged on Unity 6.1, it would be super useful for our current project. |
Sure! I'm just waiting for reviewers to be assigned to this PR. |
I just got more feedback on the Issue #3421 I'm thinking about a way to have the prefab handler "write" the payload right before the spawn happens. In this approach, I would try to move most of the logic into CreateObjectMessage, removing it from the object data. This would avoid all the newly added generics and any potential object boxing. I'm converting this PR into a draft to keep modifying the implementation and will get back to comment once it's ready for feedback again. |
[Sorry bad writting i might edit this text later] I did requested changes by @EmandM into the PR, currently im reusing the same buffer serializer from the object serialization. Also i could make the new interface to not have the OnSynchronize, and having instead Serialize and Deserialize methods, but that would make the usage not as comfortable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a fantastic next step! The video was super helpful to understand what the intention was. Is there any chance you have a more minimalistic example of this feature that doesn't require linking the items together later. The added complexity of having separate objects that are linked implies that this feature is doing the linking. My understanding is simply that this feature enables changing the object that is instantiated as part of the instantiation flow.
A few notes on the code:
Out of interest, is there a reason you chose to implement the custom data passing only on scene objects? We'd prefer a solution that works everywhere where the prefab handler can work. Again, the symmetry in the approach is important. If you can do something with prefab handlers in one area, that feature should work in all areas.
It would also be fantastic if you can add a test showing how this feature is used.
com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs
Outdated
Show resolved
Hide resolved
com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs
Outdated
Show resolved
Hide resolved
Regarding this, I've tested it, and in the video only one object is an in-scene placed object. The rest are dynamically instantiated through the prefab instance handler, not just scene objects. Maybe I misunderstood what you meant? I’ll work on a simpler example, though to be honest, the linking case is the most valuable use case I’ve found so far, its actually what motivated this feature in the first place. Right now I dont have many alternative examples because most of the benefit comes from enabling that exact flow: having objects pre-created and deterministically selected or connected based on payload metadata. Of course, it also opens the door to more advanced use cases, like sending special setup/configuration data before instantiation (For example in the video that sets up In a way, I don’t yet fully know the limits of the feature, I just know it unlocks workflows that weren’t previously possible. About the other changes, I will answer these and also come with some changes you might like tomorrow. |
I had another pass today. Definitely agree with what you've said about I also took a bit more time with the example. I absolutely see what you're doing there. Thank you for the detailed explanations. It'll be best if the function naming we go with follows the Mixing and matching from your naming options, what do you think of something like these two options? /// Option A
INetworkPrefabInstanceWithDataHandler
OnPreInstantiate()
// Option B
INetworkPrefabWithSynchronizeHandler
OnPrefabSynchronize() |
No problem, I’ll make that change in a few minutes!
Its a perfect idea, making it easier to use for developers. The next commit will include that.
Since it’s still an In contrast, Would this option work for you? public interface INetworkPrefabInstanceHandlerWithData : INetworkPrefabInstanceHandler
{
void OnSynchronizeInstantiationData<T>(ref BufferSerializer<T> serializer) where T : IReadWrite
} The method name Let me know what you think. I’ll go ahead and push a commit with these changes in the meantime and await your feedback. 😄 |
1) Moved the payload deserialization into the AddSceneObject, for deferred instantiation compatibility 2) Changed the new interface to be a direct single extended implementation, instead a complement of the handler 3) Some renames to better match what the feature does for developers
All requested changes have been implemented.
This is the same example shown earlier, but simplified and updated to reflect the new interface and naming conventions: public class TestHandlerDeterministicLink : INetworkPrefabInstanceHandlerWithData
{
Dictionary<int, DeterministicIDHolder> deterministicSpawns = new Dictionary<int, DeterministicIDHolder>();
int nextDeterministicId = 0;
int customSpawnID = 0;
public void OnSynchronizeInstantiationData<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref customSpawnID);
}
public NetworkObject Instantiate(ulong clientId, Vector3 position, Quaternion rotation)
{
return deterministicSpawns[customSpawnID].GetComponent<NetworkObject>();
}
public void Destroy(NetworkObject networkObject) => GameObject.Destroy(networkObject.gameObject);
public void DoSpawn(GameObject linkablePrefab)
{
var deterministicIdHolder = GameObject.Instantiate(linkablePrefab).GetComponent<DeterministicIDHolder>();
deterministicSpawns[nextDeterministicId] = deterministicIdHolder;
deterministicIdHolder.SetID(nextDeterministicId);
nextDeterministicId++;
}
} Marking the pull request as ready for review ✅ |
I just want to bump a quick comment to this thread that I really like the cooperation here, how this is developing and while I don't have sufficient knowledge to help more with those topics I love the energy in this thread and I'm looking forward to how it develops 👀 (having in mind that it's always complicated process with many bumps on the way) |
I've already implemented the required changes addressing all feedback:
This version aligns closely with the current architecture and feedback. I’d appreciate any thoughts on whether this direction feels more in line with your expectations, especially considering clarity, maintainability, and minimal disruption to existing workflows Additionally, I’ve prepared a branch extending this change with more ambitious improvements for future-proofing Key improvements in that extended branch: Please, check extended branch PR
|
Hello @EmandM Just checking in to confirm this is still on your radar. Let me know if you'd like me to adjust anything or clarify further. Just to clarify: the last update message includes two proposals.
The extended one builds on the base and introduces cleaner abstractions for long-term maintainability, but I kept it separate to avoid adding noise here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking really good!
There's still some tweaking of the approach needed. The HandleWrapper
pattern adds a bit too much complexity. It makes other things seem simple but at the cost of a lot of complexity in one place. I'd rather not use that approach.
I'd also rather not re-allocate the FastBufferReader
. I'll see if I get the time to look for some other approaches.
Thanks for your patience with reviews! It's been quite busy on our side.
com.unity.netcode.gameobjects/Runtime/Spawning/INetworkPrefabInstanceHandlerWithData.cs
Outdated
Show resolved
Hide resolved
com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs
Outdated
Show resolved
Hide resolved
com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs
Outdated
Show resolved
Hide resolved
com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs
Outdated
Show resolved
Hide resolved
com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs
Outdated
Show resolved
Hide resolved
Cool! I will be doing some changes this weekend. |
https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3430/files/0d62ea4e12de33452fd642387d64ad08f614348f#r2103080426 https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3430/files/0d62ea4e12de33452fd642387d64ad08f614348f#r2103077699 changes requested
I've found myself with a bit of time today. I see us going round in circles a bit for the structure and flow of this new interface so I'm going to take a deeper dive to see if I find a way through all the contradicting requirements. |
Hey @EmandM, thats nice! From my side, I’m not seeing any remaining conflicting requirements, I think they’ve all been resolved cleanly, unless there are some new internal constraints I’m not aware of yet. I think the only thing left is choosing between A or B so I can put together a commit that covers the remaining part of your last review. Quote of the message in question
|
Just to have the code in a fully updated and reviewable state, I went ahead and pushed option A for now. It felt a bit unfinished leaving it undecided, so this way the last review point is addressed and everything is ready for a clean review pass. If you prefer option B instead, let me know and I’ll switch it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks fantastic! I really like the abstract class approach. We're definitely on the final stretch here.
Next steps:
- The code needs to be formatted to match our internal format standards. You can run
dotnet run --project dotnet-tools/netcode.standards -- --fix
to auto fix what can be auto fixed. - The tests are fantastic but don't entirely fit with our testing framework. That's fine, I absolutely don't expect you to fully onboard to that. I can either fix it directly on your branch if you give me access to your repo, or I can try fix things on my side and send you the updated file. The second approach might be a bit slower though.
com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs
Outdated
Show resolved
Hide resolved
com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs
Outdated
Show resolved
Hide resolved
com.unity.netcode.gameobjects/Runtime/Spawning/INetworkPrefabInstanceHandlerWithData.cs
Outdated
Show resolved
Hide resolved
com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs
Outdated
Show resolved
Hide resolved
com.unity.netcode.gameobjects/Runtime/Spawning/INetworkPrefabInstanceHandlerWithData.cs
Outdated
Show resolved
Hide resolved
com.unity.netcode.gameobjects/Runtime/Spawning/INetworkPrefabInstanceHandlerWithData.cs
Outdated
Show resolved
Hide resolved
Awesome! As it’s more convenient for you to work directly on the tests from my repo, I already gave you access. |
Most of the feedback from the last round should be addressed in this commit:
Let me know if anything else should be adjusted. Regarding formatting: I couldn’t run the |
It makes sense to write the size separately. We shouldn't be snooping on the internals of the serialization anyway. The over-optimization bug apparently got me there. I've pushed up the formatting fixes and updated the tests to use our test framework, however in doing so I seem to have broken the late joining client test. Do you mind looking into that? |
Before syncing with your changes, the test was failing on my side too because I had forgotten to re-add the missing |
It’s fixed now. The issue was just that the Seek call didn’t include startPosition when calculating the new position, so it ended up seeking too early. Easy miss. Let me know if there’s anything else I should take care of, like updating the main post body or anything similar. Not entirely sure what else would be needed at this point. |
Solves Issue #3421
Related to the discussions in Pull Request #3419 and Issue #3421 (follow-up proposal based on new approach).
This PR introduces support for sending custom instantiation payloads through
INetworkPrefabInstanceHandlerWithData
to receive metadata before callingInstantiate()
.The feature is fully optional, backward-compatible, and requires no changes to existing user workflows.
Changelog
INetworkPrefabInstanceHandlerWithData
, a variant ofINetworkPrefabInstanceHandler
that supports synchronizing custom data prior to theInstantiate()
method call.Testing and Documentation
INetworkPrefabInstanceHandler.Instantiate()
summary updated to mentionINetworkPrefabInstanceHandlerWithData
INetworkPrefabInstanceHandlerWithData
.Deprecated API
Backport
Implementation Example
Spawning flow:
Important
When spawning, you must update the handler's data before calling
Spawn()
orInstantiateAndSpawn()
.The data set in the handler will be serialized automatically prior the instantiation process.
Highlights