Netcode DevLog Entry 2 - Singletons, Host Worlds, GameObject Mapping

It’s the new year! We’re back from the holidays and following from our previous entry, we’ll talk this time about accessing ECS singletons from GameObject logic, worlds and GameObject mapping. These are a few of the core features we have to land first to unlock the rest of the porting we’re doing.

Note: The features discussed below are experimental and currently hidden. This dev log is to keep you up to date on our development.

A Single Point of Entry for Singletons

Netcode for Entities users might be familiar with the various singleton components Netcode uses to expose various features like relevancy, server rewind, metrics, prefab management, etc. These are all a bit spread out and are hard to remember. You have to remember NetworkStreamDriver, PhysicsWorldHistorySingleton, GhostMetricsMonitor, GhostRelevancy, NetworkStreamConnection, etc.
For those looking at our implementations, you might notice a new Netcode static internal API added to Netcode for Entities’ most recent version. As we’re porting changes to it, you’ll see it grow and include more and more utilities that’ll become available when we remove our experimental scripting symbol.

This new Netcode api will be the main class you should remember when dealing with Netcode.

Without this, in order to connect from outside a system (from GameObject scripts for example), you’d need to call the following

ClientServerBootstrap.ClientWorld.EntityManager.CreateEntityQuery(typeof(NetworkStreamDriver)).GetSingleton<NetworkStreamDriver>().Connect(EntityManager, endpoint)

This will now get simplified to

Netcode.Connect(endpoint)

Some other examples

// Registering prefabs at runtime
Netcode.RegisterPrefab(myPrefab.gameObject);
// Relevancy
Netcode.Relevancy.Set(ghost, connection);   

The idea is to have an API similar to what Physics provides, with its Physics.Raycast(...) and to have this callable from both GameObject and ECS flows. An ECS system should be able to connect using Netcode.Connect as well for example.

Single-World Host

This leads to the questions “how would this work with multiple worlds?”, “If I call Netcode.Connect, which world will it use?”.

Netcode for Entities users are already familiar with the binary client and server world setup required to have client hosted servers in Netcode. This offers a good separation between client and server logic and makes it easier to test locally, since your local client world runs the same logic as a standalone client world. This also makes it easy to port to dedicated servers, since your server world’s logic is already self contained.

However, it does require running serialization systems unnecessarily and running multiple times the same gameplay logic (once on the server world and 1+ on the client world for prediction), which is not great for resource constrained devices. It also means the GameObject to entity association we talked about in the previous post spawns both client and server GameObjects in the same Unity process. APIs like GameObject.FindObjectsByType and Physics.Raycast are not world aware and would return results from both worlds. This is not great. You don’t want your client GameObjects being able to interact with server GameObjects in the same process and vice-versa.

For these reasons, we’ve introduced a new “single-world host” mode for Netcode worlds. You can think of it as a server world with a subset of client systems running on it. With this, the host player no longer needs to serialize data to itself. Visible ghosts are no longer predicted or interpolated, they are simply the authoritative ghosts being rendered.
This is an additional mode for worlds and doesn’t replace the existing binary world setup.

Netcode for GameObjects users will find this pattern relatively familiar, this is already how “host” NetworkManagers work, where NetworkObjects act as both client and server at the same time.

Behavior Differences and Migration Considerations

For existing Netcode for Entities projects that want to benefit from this new mode, we will provide migration documentation. Here’s a few important highlights:

  • When using single-world host mode, all ghosts are authoritative ghosts which means interpolation must be handled differently.
    • Instead of changing the authoritative value, it’s recommended to just smooth the visuals when interpolating. For example, use LocalToWorld for transforms.
  • You can use a fast path for RPCs in single-world host mode. Custom serialization should take advantage of that fast path with the new IsPassthroughRPC and GetPassthroughActionData.
  • Partial Ticks: single-world host doesn’t support partial ticks. You can instead interpolate your ghosts between full ticks on the host.
  • Sending snapshots on catchup ticks: in a server-only world, the server can send snapshot packets for each individual catchup tick if they all happen in the same frame. This isn’t the case for single-world host, where the host only sends one snapshot per frame.
  • There’s a few important differences regarding input management, local connections and ownership we’ll describe more in our doc once out of experimental.

For Netcode for GameObjects users

Worlds are pretty similar to NetworkManagers in Netcode for GameObjects, but are not tied to a particular scene. They are in charge of managing your current session, connection, prefabs, networked objects lists, etc.
However, you can have multiple worlds in the same process. This is actually how our automated tests for the new GhostObject APIs work, by having one server world and 1+ client world connected to it. Same as our existing ECS side tests work.

Worlds Next Steps

This was one of the first steps required to get GhostObject GameObjects to work with Netcode for Entities. This also should provide nice optimizations for entities projects. Survival Kids had to bypass Netcode’s binary world setup with their own version of single-world host to release on their target platform, so this new mode will allow a supported way of doing this.
This is a pretty tentacular change and so we’re currently working on enabling most of our existing automated tests with this new mode, to make sure all flows are supported. Once that’s done, we’ll remove the experimental define and make this a first class citizen in Netcode.

A long term goal is to have more and more engine side GameObject APIs be world aware. As you can guess, this is a massive endeavour and so for now we’ll only support single-world host mode for the new GhostObject layer.

GameObject to Entity Mapping

This is more a “behind the curtains” type of change. Another team is currently working on engine side changes for entity + GameObject flows. In order to unblock our work, we’ve added a “fake” way of doing this by keeping a static GameObject to Entity hashmap to replicate the same behaviour. Once the feature is available engine side, we’ll be able to get rid of this internal map.
We’re currently generating entity prefabs on the fly when GameObject prefabs are registered. Since there’s no entity baking, we simply create the entity and then convert it to a Netcode prefab entity using the existing GhostPrefabCreation.ConvertToGhostPrefab.

No Spawn() Method

Since entities side new ghost spawns get picked up automatically by systems, we’re aiming for a no Spawn() method solution where you simply instantiate your GameObject to spawn it server side (and in prediction client side).

var myGhost = GameObject.Instantiate(myPrefab);

For example, at editor time you can now simply drag a ghost prefab in your server side scene and it’ll dynamically spawn it on any connected client.
We’re still investigating offline to online flows, so this might change.

Automatic Prefab Registration

In order to spawn your ghosts automatically client side, Netcode needs to be aware of the prefab it needs to use. We’re currently working on automatically detecting prefabs currently loaded in memory and having all of this prefab tracking be a behind the curtain thing. For cases where you DO want to control prefab registration, a “skip automatic prefab registration” option is included on your GhostObject to allow you to control this with Netcode.RegisterPrefab(...).
The idea would be to use the same manual registration flows when you use Addressables.

Next DevLog Entry

Next entry, we’ll talk about GhostBehaviours with their GhostField state replication, the new PredictedUpdate() method and input management.

We want your thoughts! One of the main reasons we’re doing this series of posts is to chat about all of this with you guys. There were great questions in the previous dev log so keep them coming, it’s nice to chat about this publicly!

21 Likes

But Physics.Raycast is bad thing about Physics
When we want to have more than one world we can not because every plugin on store use Physics.Raycast and dont know anything about separate world so unusable

You want to have unusable Netcode api so we never can use it in few worlds?

Please dont do Static Api create Api like
World.GetService<Netcode>().Relevancy.Set(ghost, connection);

if we need we can cache Netcode into _netwode and then call
_netcode.Relevancy.Set(ghost, connection);

same for Physics and any api that is relative to world rather then global to project.
so Application.* and Graphics.* is good to be Static and not Netcode and Physics

2 Likes

Hey JesOb,

Good point, forgot to talk about this. We’re currently looking into having a new NetcodeWorld class that inherits from World. This is mostly at the prototype stage though, but the idea is they’d be the ones holding the actual implementation of Connect, Relevancy, etc. Since in most cases we expect having a single world per game, Netcode.Connect would simply redirect to the current active client or server world’s Connect.
A more advanced user that wants to have a setup with multiple worlds would then be able to just do ClientServerBootstrap.ClientWorlds[0].Connect() or ClientServerBootstrap.ServerWorld.Relevancy.Set()

This way, you still have only one thing to remember when dealing with Netcode, that “Netcode” class.

Another idea that floated around was to have a static “world context”. At the beginning of a world’s tick, we’d set the “active world” to that world. This way, if calling Netcode APIs from PredictedUpdate for example, it’d use the currently active world to do its operations. This would remove some of the boilerplate of having to always specify a world in those operations. There’s little reason for a ghost to do operations on a world other than its own, so having this global context would make it “just work”. Whether we apply this to other Unity features is a more down the line thing though. For now, we won’t support multi world with GameObject Physics, at least not for the first iteration of this new GameObject layer.

2 Likes

This is better

What about real multi world like 10 server worlds in one process?
without any restriction about what world is for what

so we can spawn as many worlds as we want and setup them in any way we want (server, client, support, loader, editor…) without unity restrict us in any way?

There is good sample of accessing local instance in .net
Thread.CurrentThread.*

So there will be good to make current world and netcode to be accessed this way
and outside world context Current must return null or exception

This way all AssetStore tools will use correct Api and will be compatible with any world they used in.

May be this is good approach ClientServerBootstrap but I prefer to have Global access point to Worlds in Unity and get Server or Client or any other one from there if I need.

World is new fundamental thing in unity and its Api must be fine tuned for every potential usage not only Netcode

Currently I can get Scene from GameObject.
I expect that at least in Unity 6.6 I can get World from scene and from gameObject

1 Like

What about real multi world like 10 server worlds in one process?
without any restriction about what world is for what

ClientServerBootstrap already has a ServerWorlds list just the same as ClientWorlds.

Non-netcode worlds is a bit out of scope of the current work we’re doing. The entities team is looking at this on their side, but nothing to announce right now.

May be this is good approach ClientServerBootstrap but I prefer to have Global access point to Worlds in Unity and get Server or Client or any other one from there if I need.

Entities already has a World.All static API to access all worlds in your process. Since it also contains loading worlds, editor worlds and the such, ClientServerBootstrap is there so you don’t have to always iterate over all worlds to find the one that interests you.

Currently I can get Scene from GameObject.
I expect that at least in Unity 6.6 I can get World from scene and from gameObject

Same as above, out of scope for these dev logs, I’ll let the entities team announce things when they are ready :smiley:

And just as a reminder, Netcode side multi world support are for later. The first iteration of this new GameObject layer will focus on single world setups, without painting ourselves in a corner when we do support multiple worlds.

4 Likes

Good to hear
Thanks :slight_smile:

For those landing here, the next entry is available here!