naia-lib / naia

a cross-platform (including Wasm!) networking library built in Rust. Intended to make multiplayer game development dead-simple & lightning-fast
Apache License 2.0
857 stars 61 forks source link

[suggestion] enable/disable replication on a per-component basis #186

Open Veritius opened 12 months ago

Veritius commented 12 months ago

The problem

The current state of replication is this:

  1. Add a component with Replicate to the Protocol.
  2. Add the component to an entity.
  3. Mark that entity to be replicated.

The component is now replicated. Great! However, if you have an entity that is replicated, but you don't want to replicate one of its components, you have no way of preventing that.

This is (as far as I'm aware) one of the reasons that Bevy components don't have Replicate implemented by Naia. If, in the current state of the system, you have a replicated entity with a Replicate-implementing component, and the entity is set to be replicated, it will be replicated whether you like it or not.

A solution

A potential solution is having typed marker components that tell Naia whether or not the component should be replicated.

Let's create a hypothetical struct, Foo. Foo stores some information that is useful to both the server, and the client, but only in some circumstances. Sometimes, the server may want to hide Foo from the client. In this case, the server will add a special marker component that indicates that Foo is not to be replicated.

Let's get out two of our hypothetical marker components, AllowReplication<T> and PreventReplication<T> Both will have the generic type T, with the trait bound of Replicate, but won't store anything themselves. If the server doesn't want to tell the client about Foo on a specific entity, it will add PreventReplication<Foo> as a component. The fact PreventReplication has Foo as T will let the replication code know to no longer replicate the Foo component.

Let's create another hypothetical component, Bar. Bar should not be replicated in most circumstances, but it may need to be occasionally. In this case, when defining our protocol, we would say it is not replicated by default, even though it implements Replicate. Then, on entities where the server wants it to be replicated, it would add AllowReplication<Bar> as a component.

There should also be a third value for whether or not a component is replicated by default, which is "yes and ignore the prevent marker component". This could be useful if you have something very important that must be replicated and you don't want anything interfering with that. Naia should probably send some kind of log if a PreventReplication component exists on the entity for that type, too.

In cases where both marker components are present, it should probably go to the protocol default for the component being replicated, or another value passed when the protocol is being set up. Or it just panics, that works too.

Here's a table of all the outcomes, to make this clearer. Neither component is present AllowReplication is present PreventReplication is present Both are present
Replicated by default Replicated Replicated Not replicated Replicated
Not replicated by default Not replicated Replicated Not replicated Not replicated
Always replicated Replicated Replicated Replicated Replicated

Why like this

The ideal outcome

In this new system for replication, if I wanted to have a component that was replicated by default, I could do the following.

  1. Add a component with Replicate to the protocol, setting it to replicate by default.
  2. Add the component to an entity.
  3. Mark that entity to be replicated.

A component replicated by default but I want to block it:

  1. Add component to protocol, set it to replicate by default.
  2. Add the component to an entity along with PreventReplication
  3. Mark that entity to be replicated.

And denied by default but allowed by the marker:

  1. Add component to protocol, set it to not replicate by default.
  2. Add that component to an entity and add AllowReplication
  3. Mark that entity to be replicated.

Some extra thoughts

Should the other side of the transaction be alerted to if replication of a component stops/starts? Should it just be removed or should the other side handle it with a system? Should this be configurable in the protocol? (most likely)

Should components be part of scope instead of this system? Probably not, it's likely a better solution to add them as child entities that are scoped to the right people. That would also probably get confusing fast.

Will this make writing prediction code harder, being that two sides may be aware of different components on the same entity?

Another solution

Snen on discord mentioned having two entities, one exclusively on the server with all information, and one that's replicated over Naia and components can be removed and added freely. This is a potentially better solution, but it sounds potentially annoying to juggle two entities and their components instead of one.