#bevy_replicon

1 messages · Page 4 of 1

willow osprey
#

sorry, i don't mean just the components that have changed, i mean if Position(123,456) has changed to Position(123,456.1) then send a 0,0.1 diff, which uses fewer bits

#

replicon doesnt do that atm right?

#

possibly using a bitmask vs a previous packet to decide what components are being referenced etc. point being holding onto packets on the server that we know the client has might be useful/necessary in future

spring raptor
#

Too tricky, you can get desync because of lack of determinism

#

Instead other technicue is usually used. You reduce precision.

willow osprey
#

quantizing too isuppose

spring raptor
#

Yep!

willow osprey
#

quantizing + sending diffs like i described should be safe though i think

#

anyway i know that's not what you were discussing sorry to derail 🙂

spring raptor
#

I would imagine to generate implementations like this using macro.

#

And tunnable via annotations

#

But it's for the future, yes

spring raptor
#

Played with the current approach + comparing result before insertion.
This requires all components to have PartialEq and I think it could cause weird bugs when you reinserted a component on server and it's not triggered on client.

shrewd sundial
#

I looked over the code of both examples and still can't figure it out

simple_box.rs: draw_boxes_system() makes sense but not sure how to apply this to a 3D scene or to entities that a player doesn't directly control; for example: explosive barrel in Half Life or a drive-able jeep in Battlefield

tic_tac_toe.rs: server panicked when I disconnect and reconnect the client

spring raptor
spring raptor
#

@echo lion looking at UE networking code. Looks like they use a channel per actor. But it's because in their case actors don't depend on other actors. So not exactly our case, but an interesting read.

#

I sent only headers because they have comments and the code is quite complex.

spring raptor
#

Their RPC (remote procedure calls, like events in our code) are all sync, but in a different way. They always attached to an actor.
I think in our case would be great to keep events as is, but for server events include server tick and apply event only after this tick. This way you will never get an event too early.

spring raptor
shrewd sundial
# spring raptor I don't understand your question. What is unclear to you? Have you read the quic...

If the server spawns a entity and a client joins later. How should I send a message to the client so that the entity exists on both?

In the last day or so I wrote it as a ServerEvent but I assume there's a better way

#[derive(Component, Deserialize, Serialize)]
struct PhysicsCube;

#[derive(Debug, Default, Deserialize, Event, Serialize)]
struct SpawnCubeLocallyEvent(Color, Vec3, Quat);

fn server_event_system(
    mut server_event: EventReader<ServerEvent>,
    mut connected_event: EventWriter<ToClients<SpawnCubeLocallyEvent>>,
    existing_cubes: Query<(&mut Transform, With<PhysicsCube>)>,
) {
    for event in &mut server_event {
        match event {
            ServerEvent::ClientConnected { client_id } => {
                for cube in &existing_cubes {
                    connected_event.send(ToClients {
                        mode: SendMode::Direct(*client_id),
                        event: SpawnCubeLocallyEvent(
                            Color::RED, //TODO: Get color
                            cube.0.translation,
                            cube.0.rotation,
                        ),
                    });
                }
            }
...
        }
    }
}

fn event_receiving_system(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut spawned_cubes: EventReader<SpawnCubeLocallyEvent>,
) {
    for cube_data in &mut spawned_cubes {
        let color = cube_data.0;
        let position = cube_data.1;
        let rotation = cube_data.2;

        commands.spawn((
            PbrBundle {
                mesh: meshes.add(Mesh::from(shape::Cube::default())),
                material: materials.add(color.into()),
                transform: Transform::from_translation(position).with_rotation(rotation),
                ..default()
            },
            PhysicsCube,
        ));
    }
}

spring raptor
shrewd sundial
#

Should I mark things like mesh & material components for replication also?

sharp roost
#

You should look at the Blueprint pattern in the readme

#

Basically instead of replicating meshes and materials you just replicate the information needed to construct the mesh/material on the client

shrewd sundial
#

I think it just all just clicked in my head. Thank you very much

spring raptor
spring raptor
spring raptor
# echo lion sounds fine

What would you use for cache? I need a way to iterate and drain values with specific tick. Looks like there is extract_if on Vec, but it's nightly.

echo lion
#

Can use BTreeSet and the split_off() method

echo lion
spring raptor
echo lion
#

only reason is automatic sorting

#

if you assume the vec won't be that big then iterating an unsorted vec is fine

spring raptor
#

I just thinking that events will be ordered if users specified it.

#

I.e. configured the policy

echo lion
#

ah, also inserting/removing doesn't require modifying the rest of the structure

spring raptor
#

Probably ListOrderedMultimap is better suited for it, yes...

spring raptor
#

Force pushed to fix a typo in commit name

#

Nice suggestions, thanks!

grave yarrow
#

I've been gone from this for a while but I'll probably be working on the prediction somewhat soon

#

Got wrapped up in physics stuff

#

Is the world diff stuff still wrapped up with changeticks?

spring raptor
spring raptor
#

We come a long way since last time you checked. So many things changed.

spring raptor
#

It makes custom events a bit more hard to write, but I think it's fine.

spring raptor
#

@echo lion sorry for the ping, but I didn't receive a review or approval. By any chance you forgot to submit it?

echo lion
spring raptor
viscid jacinth
echo lion
spring raptor
#

Found an odd behavior. We do not send an entity if it doesn't contain any components except Replication. But we do send a despawn for it becasue we check for any Replication removal.
It's not critical since nothing bad happens, client will ignore the despawn. Should we address this?

echo lion
#

I think it can be ignored, if that’s an issue for someone it’s probably a bug in their code

spring raptor
frosty light
#

I've just started working with replicon and was wondering if there were any deafult replication components for transform. At the moment, I've been creating a transform object for my world on the server, replicating to the client, and then converting back into a Bevy Transform object. Is this the recommended way of doing things, or is there a module I've overlooked that have some of the core bevy components already set for easy replication?

spring raptor
#

See examples in the quick start guide in docs

frosty light
#

Thank you for the redirect! I'd over looked that before.

spring raptor
#

@echo lion why do you think that min_tick_update_system::<T> and sending_system should be chained?

spring raptor
#

Thanks for reviews!
Will draft a new release today.

spring raptor
eternal glacier
#

Started to use this crate today, and I just want to say, awesome job!

spring raptor
spring raptor
willow osprey
spring raptor
viscid jacinth
#

Is there some kind of tick-synchronization that would make client prediction possible?
As far as I understand, the client would need to keep track of which tick it's currently on compared to the server tick, so that when client sends an input to the server; the server understands which 'tick' it corresponds to.

I don't really get how this part is handled. Even for this write-up: https://github.com/RJ/bevy_timewarp#example-rollback-scenario
how does client know that it is on frame 10 (compared to server's 6)?

GitHub

A rollback library that buffers component state. Useful for netcode. - GitHub - RJ/bevy_timewarp: A rollback library that buffers component state. Useful for netcode.

willow osprey
#

you need to get your clients ahead such that they can send inputs for a frame, and have those inputs arrive at the server 1-ish frames before the server simulates that frame. so the clients end up receiving updates for the server for frames they've already simulated, which is when you potentially rollback

#

(does include some slightly unhinged late-night note taking too..)

#

when clients connect to the server, the server reports current tick, and based on latency calculations the client calculates which tick the server is currently on at the time it receives that packet.. then it adds enough to that so that its inputs will arrive at the server on time, and sets the tick at which local simulation will start. after that, it should mostly stay in sync. however to correct any drift i do what the Rocket League GDC video describes as "upstream throttle".

GDC

In this 2018 GDC talk, Psyonix's Jared Cone takes viewers through an inside look at the specific game design decisions and implementation details that made the networked physics of Rocket League so successful.

▶ Play video
granite hill
#

@spring raptor Created a PR for the bevy 0.12 upgrade using the master branch of renet.
Everything seems to work fine for me but one test (scene_update_sync) is failing.
I also added some questions/comments
Maybe it can serve as a starting point for the update 🙂
https://github.com/lifescapegame/bevy_replicon/pull/112

GitHub

Resolves: #110
Implement support for bevy version 0.12.
Using the master branch of bevy_renet right now until they publish a proper release.
Main Changes

iterate events using .read()
Use EntityMut...

viscid jacinth
#
  • Yea i get that server sends its current tick, but i'm not sure how the client knows which frame it is compared to the server; I guess it just counts the number of updates it did compared with the last received 'server tick' ? Or it just sets its tick as server_tick + RTT/2 + buffer
  • another thing I don't really get is that here you refer to ticks as frame_numbers, because replicon runs once per frame. But I see in your notes you increment your tick during a fixed update system that runs prior to the physics fixed-update. So there's a frame tick, and a fixed-update tick? I understand pretty well the basic of client-prediction/snapshot-interpolation (that client is ahead of server by RTT/2 + buffer, and that when a server updates arrives we re-simulate the client inputs that are more recent than that). It's reconciling those concepts with the fixed-updates systems and how to synchronize time between client/server that I have trouble understanding. I'll read a bit more through your code, but i'd love to have a chat at some point! Thanks for the video also
spring raptor
# viscid jacinth - Yea i get that server sends its current tick, but i'm not sure how the client ...

On client you need to track it yourself if you want prediction, see https://github.com/RJ/bevy_timewarp/blob/main/REPLICON_INTEGRATION.md.
You can configure how it "ticks". By default it's per frame, but you can configure it, see https://docs.rs/bevy_replicon/0.16.0/bevy_replicon/server/enum.TickPolicy.html.
If you want to run it in FixedUpdate, just set Manual and just add https://docs.rs/bevy_replicon/0.16.0/bevy_replicon/server/struct.ServerPlugin.html#method.increment_tick to your FixedUpdate.

#

Edited the message^

willow osprey
#

(i will explain what i'm doing better tomorrow from my desk 👋)

willow osprey
#

both my client and server use fixed update, and i refer to each fixed update step as a frame (although it's not strictly tied to rendering framerate).
when the client gets the welcome message from the server, the message contains the server's current frame. using rtt latency calculations, and knowing what the fixed timestep is set to, allows the client to calculate what frame the server will currently be on, at the time the client processes the welcome message. ie the frame from the message + some, based on packet travel time etc. frames might be 1/60 th second each at a 60hz fixed timestep., so you can calcualte how many frames elapsed while the packet was in flight.

the client then needs to add a few frames to that number, to set it's own starting frame, because the client needs to be simulating frames ahead of the the server, so that inputs for specific frames arrive at the server just in time for the server to process that frame. the server never rolls back. if inputs for a frame don't arrive at the server in time, the server has to guess or assume no inputs.

i calculate how far ahead to start the client based on latency and try to have it so i can sample inputs for a frame, send to server, and have them arrive just in time. so server will get my inputs for frame N when the server is currently processing frame N-1.

at the start of every FixedUpdate, when my game systems run, clients always do game_clock.advance(1); to advance their frame. (assuming not in a rollback)
the server does this:


pub fn frame_inc_and_replicon_tick_sync(
    mut game_clock: ResMut<GameClock>,
    mut replicon_tick: ResMut<bevy_replicon::prelude::RepliconTick>,
) {
    game_clock.advance(1);
    let delta = game_clock.frame().saturating_sub(replicon_tick.get());
    replicon_tick.increment_by(delta);
}
#

so the server's frame (in game clock) advances in lockstep with replicon tick. that is the only time replicon tick changes, i have it set to manual

#

and replicon tick changing is what causes replicon to send out a load of replication data, in which is embeds the current RepliconTick

#

so when the client gets a replicon packet, it knows what frame the values are for (replicon tick = frame)

#

@viscid jacinth does that shed some light on what's going on?

#

(that delta should always be 1 btw, but you can't set the replicon tick directly, you have to increment it by a delta)

#

btw at no point do i ever send a clock value in seconds between server/client, just frame numbers. the server keeps track of how many frames ahead or behind inputs arrive at. ideally client inputs should arrive 1 frame ahead. if they arrive too late they are useless. this number is sent back to clients, and used to slightly speed up or slow down the client simulation by modifying the FixedTime. so fixedupdate might run every 15ms to speed up the simulation, even though physics and everything is assuming 16.666ms (for 60hz) have elapsed. i have the physics library set to advance manually assumgin the true fixed timestep has elapsed too, so i'm always simulating 16.666ms in the physics step. that's what the rocket league vid describes as upstream throttle i think.

viscid jacinth
#

thanks this is helpful.

  • so you run ServerSet::Send in the FixedUpdate system, which means that the network fixed timestep is the same as the physics fixed timestep?
    • also it means that replicon messages are sent at each fixed-update system frame (so potentially multiple in the same render frame?)
    • that's potentially a lot of packets sent, what about if we want to only send packets every 50ms, but the physics timestep is 10ms? (i.e. what happens if you want the network timestep to be different from the physics timestep?)
  • do you ever recompute the client's frame number? I understand that you sometimes speed up/slow down the client time so that client message at client frame N arrives on server at frame N-1; but what if the client's RTT is suddenly widely different (they switch to Wifi)? In your current design the client would have to permanently speed-up.
willow osprey
#

ServerSet::Send still runs in the default place for me, PostUpdate. but it only runs when resource_changed::<RepliconTick>() anyway, so yes it runs once per fixed update frame (in postupdate), because that's when i advance the replicontick on the server.

yes it means network messages sent every frame from the server, after physics runs. because that's when there's useful data to send. i think a 60hz network tick rate is fine, although i would like to be able to tell replicon not to send updates so often for specific entities (not supported atm tho). client needs to send packets every frame, because it's sending inputs for every frame.

i've not thought about how to decouple the physics/network timesteps. probably possible somehow but i'm not currently doing that.

i don't really recompute the client's frame, i just speed up or slow down based on the server feedback saying how early/late my inputs are arriving. that tends to get back into alignment pretty quickly. however if the client lags enough that the updates arrive so late they are outside the rollback window, i probably need to snap the clients frame and discard some update packets. that still needs testing, not sure the beat approach yet.

#

what i'm building is still in flux, and i'm still figuring this out as I go. so keep that in mind i guess, by no means do I know the best way to build this sort of thing

spring raptor
echo lion
#

Ok

spring raptor
#

Damn, there is a mistake in renet code that prevents us to become transport-agnostic :(

spring raptor
#

I drafted a new release as is to let users update to 0.12 easily. Will open PR to the Renet repo.

echo lion
#

112 looks fine, thanks for the release

spring raptor
#

@willow osprey if you implement a networking crate that is transport-independend and provides memory-efficient API, I would love to switch replicon to something like this, especially since renet author is not very active. The switch should be quite easy, so ping me and I will do it.
Will be interesting to compare benchmarks with memory reuse.

willow osprey
#

i'll def be asking for advice on the allocation stuff.. gonna make it actually work with minimal features first and passing my soak tests with crappy network simulator

#

once i have good test coverage and it's working will be easier to hack it up to change allocation strategy

spring raptor
#

Makes sense!

spring raptor
echo lion
#

@spring raptor I don't have brain space to work on it right now so go for it. In a couple weeks I should be at a spot where I can do it if you don't want to.

spring raptor
#

Regardless your questions: yes, I think we should reset tick to zero on gain and despawn on loss

spring raptor
#

Because rooms is quite important feature.

spring raptor
echo lion
spring raptor
#

Right, in this case we won't need to remember the visibility change... But I'm not sold to this idea unless we find a solution to avoid sending multiple updates for the same component in sequence...

echo lion
#

Hmm it's very hard

spring raptor
#

Yes, thinking about it right now. I was going to suggest to strip changed components from previous buffers, but we can't do it because user may receive only the previous buffer with the stripped data.

#

We can do smarter - do per-tick based replication until we find a new change for any unacknowledged component. And in this case - drop all previous buffers and construct a new one.

#

But it won't solve the "problem" for rooms, only for manual packet fragmentation.

echo lion
spring raptor
#

And too much bookkeeping

echo lion
#

Also if you are discarding old ticks continuously, the client may never ack a full tick if they only receive some fragments of each tick.

spring raptor
#

Also true

echo lion
#

Which also seems to be a problem for continuously-updating room-based fragmentation.

spring raptor
#

Oh, you mean if the diff is too big and gets fragmented, you may never receive it? With the current approach I mean.

echo lion
#

There may be a hard limit to the tradeoff between reliability and de-duplication.

echo lion
spring raptor
#

I looked into other libraries and looks like they send only the latest data. For old games it was okay.
In Unreal looks like they just prioritize what to send in a message based on player position.

echo lion
#

I think tick-based replication is a really solid solution for everything except when components are modified frequently (in which case stale diffs will be sent alongside fresh diffs).

spring raptor
#

Yes, it's the safest solution, but it's bad for bandwith if you change data frequently.
Having rooms to fix bandwith is better for bandwith, but highly unsafe.

In Unreal looks like they use something like rooms and just prioritize data they send. I currently thinking that in theory we could provide a higher level abstraction over rooms to split networking data into chunks.

echo lion
#

Do per-client room prioritization? Hmm

spring raptor
#

So I would consider rooms like a low-level API. And provide something on top of it, like based on position or based on bevy relations

#

So rooms are separate messages

echo lion
spring raptor
#

Yes, yes. I honestly would expect renet to do it for us, but yes.

#

Oh, you meant a bit different thing

#

Inside the room prioritize data. Hm...

#

I thought at first that you talking about sending as is and just print warnings if there is a lot of data in a single room.

#

And ask users to split data into rooms more granulary.

echo lion
#

I do not mean prioritize data inside a room, that introduces partial replication again.

spring raptor
#

Then we talking about the same thing, good.

spring raptor
#

Like in Minecraft-like game users will split world into chunks and assign a room for each chunk.

echo lion
spring raptor
#

But in essense yes.

echo lion
echo lion
spring raptor
#

Oh, we probably use different terminology. And I probably used a bad term for what I meant, sorry.
Under prioritization I meant to assign rooms in a way to send only necessary information. For example, in something like Minecraft you send only nearest chunks.

But what you saying is also a nice thing - configurable priority for each room.

echo lion
spring raptor
# echo lion Oh, limiting room membership based on current bandwidth/bandwidth limits? Hmm I ...

Sorry, I probably explained it wrong again.
I suggest that in replicon we only look for rooms and form packages based on it. And prioritize room themselves based on bandwidth limit (probably as part of separate PR as additional feature). So exactly as you saying.
And users can write their algorithms to assign rooms. For some games it's good to do it manually. For other games it could be based on distance. It could be a separate crates maintained outside of replicon (I thought about having some built-ins for this in the beginning, not sure if it's a good idea).

#

When a room have a big diff, we print a warning.

spring raptor
#

@echo lion I talked to the naia author and he uses the following:

  1. All spawn/despawn/insert/remove sent over reliable channel.
  2. All component changes sent over unreliable channel and packed tightly into packets. And client will wait for the corresponding tick from 1 to arrive first. And can apply everything received because the most important information from 1 is arrived.

Sounds like the best of both words: no partial updates (some components could just have older state) and free manual packet fragmentation. What do you think?

echo lion
#

I think jank caused by prioritization is ok, but not jank caused by unsynchronized packet merging.

spring raptor
echo lion
#

Packet loss can happen at any time and at any density.

spring raptor
#

Actually yes, makes sense.

spring raptor
#

I.e. not jittering should happen.

echo lion
#

Imo entities in the same room should be synchronized. For example, replicating a swarm of birds.

spring raptor
#

Just imagined swarm of birds junky flying because of packet loss 😅

#

Okay, then rooms for packet fragmentation.

echo lion
#

Not a great example since you wouldn't want to replicate swarms like that (you'd replicate swarm paths/commands), but it demonstrates the issue with inter-entity jank.

spring raptor
#

Right

#

I just exploring other solutions to make sure that in replicon we doing the right thing.

spring raptor
echo lion
#

Can you sketch the full design/algorithm? I feel skeptical, but also don’t see good alternatives no matter how much I think.

spring raptor
#

With the current solution we will potentially bigger packet loss, unless user will split data into rooms.
With this solution we will have junky replication by default, unless user will group data.
Which default is better?

echo lion
#

How does Unreal do throttling? Is prioritization set before building packets (so priority is a filter when scanning the world), are packets rebuilt if they are too big (with a tighter filter), or are packets built in order of data priority until they are full (implying data is accessed sorted by priority somehow)?

spring raptor
# echo lion Can you sketch the full design/algorithm? I feel skeptical, but also don’t see g...

Here is how I understand it.
The idea is to have two replication channels: reliable and unreliable.
We iterate over the world and form two buffers:

  1. For reliable channel. We collect spawns/despawns/insertions here and the current tick. For this one we collect only incremental updates, works essentially your per-tick based replication, but instead of manually track acks and reconstruct we just user reliable channel.
  2. For unreliable channel. We collect only component changes since creation (excluding their creation, we already sent it over reliable channel). Here we send data since the last ack as we do now, but try split data into multiple messages about packet size. But we need to always make sure that entity data always end up in a single message.
    Client applies updates from reliable channel first, then it reads unreliable channel. Updates from unreliable channel should be held like events until their tick arrives over reliable channel.
    This sholdn't break user code, but could introduce junky replication on packet loss. But this could be avoidable by introducing rooms and group messages not only by entities, but also by rooms.
    Another disadvantage is that we will need to track acks per entity, but we need to do it anyway for rooms approach unless we switch to per-tick replication as you suggested. But I never seen it used in game engines and I a bit worried about overhead of components that changed often.

About throttling i'm not sure, it requires quite a deep dive into the code in order to understand this.

echo lion
spring raptor
echo lion
echo lion
# spring raptor You mean for the current approach? Or for any approach?

Ok here is a sketch. The main idea is to only ack a tick when a full cross-section of rooms has been received by the client. You still get inter-room jank because a room diff for a new tick can be used to ack an old tick, but you don't need to track acks per room. This does mean relatively higher bandwidth usage to send room data for ticks that the client has already received.

I had an idea for why we need to track the number of room exits, and I think we might need to send 'empty room' after a client leaves a room until the leave-room tick is acked, but I can't remember why right now lol.

#

I'll take a break then try and remember why room exit tracking is needed.

echo lion
#

Maybe the worst part about this general approach is needing separate packets for tick data and component data. Maybe we could give people some options to optimize packets if they can assume there will be no fragmentation.

spring raptor
echo lion
#

Hmm I don’t think so, although it’s an interesting question what happens if you have an entity that moves rooms…

#

My idea is different from what Unreal does (as you described it). Unreal merges component diffs once the reliable tick is received, but in my case we only merge once we have a ‘full view’ of a tick (as represented by a full set of room diffs that intersects with the tick).

spring raptor
#

(visibility-vise, not partial replication)

echo lion
#

Let me think about it, I suspect it’s solvable.

spring raptor
#

This is what naia does, btw

echo lion
#

Hmm I think you need to track acks per-Entity in the client otherwise a component diff from one room can overwrite a newer component diff from another room. I’m not seeing a need for tracking it on the server though.

echo lion
#

Maybe per-entity tracking is only needed when merging diffs for a single tick. Merge diffs in order of age (youngest first), and don’t merge a component diff if you already encountered its entity in this merge pass.

#

Another thing to consider: if we can merge multiple ticks at once, we need to do all the spawns/despawns in order in case a visibility despawn removed some components that are not present in a visibility respawn.

spring raptor
#

@echo lion I think we should separate concept of rooms and entity grouping in messages.
For example, you probably don't care about other player characters to arrive together in one message (i.e. you don't care about their position or health to arrive in sync), but you probably want to to let players see each other. This can be done purely with rooms, but it's not quite convenient.
So what if we consider rooms as something that only affects "visibility" of the world for client? I.e. client will only receive entities from its rooms.
And introduce separate concept, let's call it "grouping" to group entities together in one message.

Let's focus on packet fragmentation for now, looks like a more important feature, we will add rooms on top later. I thought to solve fragmentation with rooms at first, but probably it's not a good idea.

Now about acks. Not sure if understand you, but here is what I have in mind:

  1. In reliable channel we don't ack anything, transport does it for us. When client connects we send the entire initial state. And after it we continue to send new spawns / insertions / despawns.
  2. In unreliable channel we remember which entity we put in which package. And on client we ack messages. For each entity on server we store a bitset of acked components. When server receives ack he marks all components from the entity as acked. This also allows some optimizations, like old resending messages without recreating them. And in this case we send all unacknowledged changes each time.
    Do you have something like this in mind or something different?
spring raptor
#

Instead of bitset it's probably better to just store last tick per entity. Will be easier to implement and more efficient.

echo lion
#

I think a room vs 'grouping' distinction would make the API too complicated, and the implementation too difficult to get right. I will try to do a proof of concept for the design I have in mind, if you also want to make a proof of concept then we can compare them.

spring raptor
#

And this is how naia is doing it.

#

Doesn't sound too hard to implement

#

But for the first approximation I would implement just the mentioned approach and add grouping in separate PR. And then rooms. Don't see flaws that could prevent implementing it on top.

But I will wait for your suggestion first of course.

echo lion
# spring raptor But for the first approximation I would implement just the mentioned approach an...

Ok so there are three separate concepts we are working on:

  1. visibility: which entities should be spawned/despawned on clients
  2. synchronization: which entities should have synchronized component updates; determines which entities go in which fragments
  3. prioritization: which entities should be updated when there is limited capacity for diffs

So yes, 1 and 2 can be separated if you want although I think in practice few people would want to.

My idea for 3 is to let users inject a custom system param that takes in an entity and spits out a priority. That priority is compared with a per-client congestion score internal to replicon and the entity diffs are ignored if necessary. If the congestion score gets maxed-out then replicon starts throttling the client by reducing the frequency of tick updates.

echo lion
#

Btw @spring raptor, how does the current replicon code clean up despawns/component removals on a client after the client reconnects, for despawns/removals that happen while a client is disconnected? I think the client needs an additional reconnect_system which despawns all entities with Replication before the first call to diff_receiving_system after a client reconnects.

spring raptor
# echo lion Ok so there are three separate concepts we are working on: 1) visibility: which ...

I would say 4, we also want manual packet fragmentation. Initially I wanted to solve it with rooms, this is why I brought the talk about it.
But currently I thinking more towards the soluiton with 2 replication channels. So I think that I should start with fragmentation, but also keep 1-3 in mind to make them fit the design. Looks like this new approach is pretty standard, so it should and naia have all of this.

So yes, 1 and 2 can be separated if you want although I think in practice few people would want to.
I just thinking that people more often will need rooms without grouping by default and apply grouping in some rare cases... But okay, let's consider when we get to it.
My idea for 3 is to let users inject a custom system param
This is clever, I like it!

I think the client needs an additional reconnect_system which despawns all entities with Replication before the first call to diff_receiving_system after a client reconnects.
I think it's convenient to have, I like it. Feel free to send a PR or open an issue and I will add it later.

echo lion
#

Manual packet fragmentation is the same as synchronization. You are deciding what updates are synchronized.

#

I just thinking that people more often will need rooms without grouping by default and apply grouping in some rare cases... But okay, let's consider when we get to it.
Rooms is a form of grouping already.

#

I think it's convenient to have, I like it.
It's not just convenient, the current code is broken without it.

#

Also, I disagree about using the normal reliable channel for spawns/despawns/etc. We should use the custom tick-based approach so we don't need to worry about the resend time causing glitches. Since the resend time is a global setting.

spring raptor
# echo lion Manual packet fragmentation is the same as synchronization. You are deciding wha...

I would consider manual packet fragmentation as a mechanism that split packages. And grouping is additional feature / rule for packet fragmentation. I.e. we can totally have manual packet fragmentation with the default strategy to group data per entity and later add grouping feature that will allow users to bind entities together.

Rooms is a form of grouping already.
Not exactly... Remember my example about players? You probably see all of them and you don't care about data to arrive together. With rooms only I would need to create a room for each entity and add a client to all of them.

It's not just convenient, the current code is broken without it.
But why broken, can't despawn all entities before disconnect?

Also, I disagree about using the normal reliable channel for spawns/despawns/etc. We should use the custom tick-based approach so we don't need to worry about the resend time causing glitches. Since the resend tim is a global setting.
Why resend time will cause glitches?

echo lion
#

I would consider manual packet fragmentation as a mechanism that split packages. And grouping is additional feature / rule for packet fragmentation. I.e. we can totally have manual packet fragmentation with the default strategy to group data per entity and later add grouping feature that will allow users to bind entities together.
My point is it all falls under the category of synchronization. You can say there are sub-categories: manually group entities per-tick, assign entities to groups, let rooms == groups, etc.

spring raptor
#

Oh, got it.

echo lion
#

Right now everything is synchronized, fragmenting breaks the replicated data into smaller synchronized chunks/groups.

#

Rooms is a form of grouping already.
Not exactly...
Ok fine, rooms can be a form a grouping.

spring raptor
#

Sorry, I bad at terminology because I'm not a native speaker.

echo lion
#

But why broken, can't despawn all entities before disconnect?
Where in the docs does it say you need to manually clean up replicated entities after a disconnect?

spring raptor
#

I 100% agree about the suggestion with the disconnect system.

echo lion
#

Why resend time will cause glitches?
Right now the resend time for replicated data is 'every tick'. If you use the reliable channel then the resend time is whatever renet resend time setting was set by the user. Resend time introduces more delay for lost packets, but our goal with replication is to minimize delays, so we should send the 'meta packet's (tick number, spawns/despawns/etc.) every tick until acked.

spring raptor
#

I see, this does make sense. I wish we could have a configurable delay per-channel.

#

Okay, if you agree with this new concept, here is my plan:

  1. Implement proof of concept with reliable channel and without any features.
  2. Switch to unreliable channel as you suggested. Maybe other suggestions come up too.
  3. Implement grouping/rooms, etc.

I just prefer to work on scoped things, don't like when everything at once in a single PR. Will also be easier to review for you.

echo lion
#

That plan is fine, but I still disagree about per-entity ack tracking so I will work on my own alternative solution.

spring raptor
echo lion
#

Yes I want to ack only a tick when the client has a full view of entities replicated in a tick. However I need to think a lot more about how to do it right, there are holes (like what if prioritization temporarily makes an entity un-replicated?).

spring raptor
# echo lion Yes I want to ack only a tick when the client has a full view of entities replic...

But why so? Yes, you won't need to track acks per-entity (and it's nice!), but at expense of bandwidth. I think having a hashmap with entities and their tick should be cheap. If I remember correctly, Bevy have a fast hashmap for entities.
Also tracking acks per-entity opens some cool optimizations, like if you remember what entities you stored in a message - you can reuse it. Or even some parts of the message if you remember write positions (this is what naia does).

Also will your approach work if not client, but entity will change the room?

echo lion
#

I feel like it would be more efficient and cleaner. I’ll think more…

spring raptor
#

Take your time, I’m not starting working on it yet anyway, will be busy this week.

echo lion
#

Oh is it per-channel? We can set it to zero then perhaps

echo lion
# spring raptor But why so? Yes, you won't need to track acks per-entity (and it's nice!), but a...

I think if we can optimize by doing the message-reconstruction approach when a message doesn’t need to change, then the design you are advocating degenerates to the tick-based design I originally proposed under these conditions: 1) all entities are in the same sync group, 2) replicated data is changed infrequently (including visibility changes), 3) all entities are given max priority which means replicon can only throttle by reducing replication tick rate.

#

So if we can fit in that optimization, I will be satisfied 🙂

grave yarrow
#

@willow osprey Couldn't you still do backwards reconciliation with timewarp? It is different from classic quake style netcode but timewarp is how modern FPS games with backwards reconciliation

willow osprey
# grave yarrow <@140137915270430720> Couldn't you still do backwards reconciliation with timewa...

for the server to do backwards reconciliation you need to be able to rewind time to a specific point, which timewarp should be useful for. i think you also need to factor in the client's interpolation between two frames, too, which timewarp doesn't have any concept of. so you'd need to consider the client's interpolated position between two frames at the time they fired a shot, and restore state to the interpolated point between those two frames i think, then do your hit confirmation check.

neat orchid
neat orchid
neat orchid
#

Ok I have the simple_box.rs example working for the server using web transport but setting up the wasm browser client code is rough.

grave yarrow
willow osprey
#

in quake style the clients are behind the server

#

since they are showing player positions interped between two snapshtos

#

just the local player pos is predicted i think

grave yarrow
#

ya I suppose the term backwards reconciliation isnt the term i should use

#

I was just relatively confused by the sports games portion of the readme since it initially made me think it was something different

#

but I believe the rollback with server behind and clients ahead is how most "realtime" networked games operate nowadays

willow osprey
#

i thought competetive fps shows you player positions interped between snapshots, even though your own player is predicted ahead somewhat

#

since that's the fairest way to confirm raycast style shots

grave yarrow
#

they do, but it's still ahead of the server so it only checks it when the servers reaches that tick

#

no need to rewind the server basically

willow osprey
#

ah

#

well they can't be ahead of the server if they have to wait for two snapshots to interpolate between?

grave yarrow
#

well sort of, it is still the local player inputs are repeated when it receives a snapshot

#

for some games they will extrapolate other players movements

#

but depends

willow osprey
#

yeah pretty sure fortnite does it differently to counter-strike

grave yarrow
#

they'll still have the basic premise the same of server is behind and collects inputs from players until the cut off of the server actually processing the inputs

#

the interpolation would just be a smoothing factor for packet loss and such

#

I think the other player positions is just the tricky part here because they could change drastically

#

with <60 ping it shouldn't be noticeable with extrapolation though unless you have some crazy movement in your game

willow osprey
#

i think based on server's estimate of your latency, it tries to calculate exactly which two snapshots, and the interp point you were using, at the time you fired a shot. then it can wind all other player positions to that moment in time and confirm the hit

#

in the traditional quake style anyway

#

extrapolating other players' positions can lead to perceived unfairness, since you can have your crosshairs on someone, shoot, and the server denies it. because your client extrapolated that player's position wrong

#

that Never Happens (tm) with quake style..

#

(although you have the shot behind cover issue sometimes)

grave yarrow
#

true

willow osprey
#

i think extrapolation is probably ok with eg rocket launchers, but not with railguns

grave yarrow
#

by sports games (in the readme) you do mean stuff like counter strike and such though right?

willow osprey
#

no, sports games more like rocket league where players interact with a moving ball

#

counter strike is traditional quake style fps

#

in multplayer football for example, you need extrapolation of players and the ball, or collisions are janky af. you can't nicelt collide with objects you are only seeing the past positions of

#

but with a mostly static world with players running around and insta raycast weapons, quake style with interping player positions between snapshots gives the best results, or so i heard. not built that model myself

#

replicon is a good building block for quake style imo, you "just" need to figure out the interpolation and the server reconcilliation parts.. heh

grave yarrow
#

I don't really need quake style I don't think

#

I'm just trying to figure out if maybe there is a way to do both of these

#

also trying to figure out if valorant/cs actually do interpolate

willow osprey
#

pretty sure they do, that's how the source engine works iirc

#

for CS anyway

grave yarrow
#

definitely seems like they rewind here I guess

#

but they are also behind other players

#

so it seems like they do both

#

actually never mind that implies they aren't extrapolating I think

#

so yeah I think you are right there

#

I still think you could make both of these work at the same time though, extrapolate most of the game world except for other players

#

or in rocket league style still extrapolate player related entities as well

willow osprey
#

yeah i think hybrid models are possible, like maybe you want to extrapolate parts of the environment that aren't cruicial to hit detection

neat orchid
crisp stump
#

CS certainly uses interpolation, thats what the cl_interp command is for

grave yarrow
#

gotcha

crisp stump
#

fun history, there used to be an explot in source engine. the value of cl_interp was by how many econds or whatever to interpolate, so if you made a keybind to set ur interp from 0 to .5, you could make the game go back in time, so if u were watching dust2 doors from t spawn, if u saw someone pass, then hit keybind, and u see them again and take an easy shot

#

but it was patched...

#

tldr dont let players modify interp while in game

grave yarrow
#

I feel like that is more an issue of taking the clients word on things rather than using a server estimate

neat orchid
# crisp stump this is really awesome, please share the code when you feel comfortable, and gre...

Ok I am able to get the server running, and the web client running however getting the web client to connect to and talk to the server has been a challenge.

I believe I have got it setup and working however it is extremely slow and also the WASM file is 100MB which seems unreasonably large.

I attached a log of the wasm server for info, also I am getting a warning in the server process saying the following

2023-11-21T23:34:48.740025Z  WARN h3::proto::frame: Unsupported setting: SettingId(
    0xffd277,
)```

If you would like to try this out (and maybe help debug) try the following steps. It is still very much a mess but right now I am trying to get a MVP.

1. Go and clone https://github.com/Zackaryia/bevy_replicon/tree/test (**MAKE SURE ITS THE TEST TREE**)
(While your at it do a `cargo clean` and `cargo update`)
2. Install mkcert https://github.com/FiloSottile/mkcert
3. run the file `examples/generate_cert.sh`
4. `cd examples/simple_box_wasm`
4. run `cargo run --no-default-features --features server` to start up the server
5. run `CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER=wasm-server-runner RUSTFLAGS=--cfg=web_sys_unstable_apis cargo run --target wasm32-unknown-unknown --no-default-features --features client` to start up the server for the WASM client. Once it has started it will link you to a website, open it in Firefox, Chrome, or Chromium (I had issues with other browsers YMMV)
6. It should all be working and in sync but when I tried it it was EXTREMELY slow and also I never really saw anything happen. There is very likely many bugs still.
spring raptor
#

You can disable it on patched relicon side.
But it also involves fixing some comiler errors because it coupled with this this layer. But it's a few lines of code, should be quite easy. I didn't do it only because I needed two last commits that didn't get into release.

neat orchid
#

Do I make a transport layer enum in Renet that has members UdpSocket and WebTransport

#

Or actually would it be an enum of

NetworkClientLayer {
  NetcodeClientTransport
  WebTransportClient
}```

Actually probably just a feature flag that flips between them is probably best kind of like how XPBD handles using f64s
willow osprey
#

what do you mean by private replicated components?

spring raptor
#

Never thought about something like this... Why not use events for it thought? You can store the received data in components locally.

woeful saffron
#

And I use it to make cheating harder and maybe reduce bandwidth

#

And for replicated resources, I use a "global" entity with components as "resources"

echo lion
#

I’d be concerned about the perf cost of checking the visibility of every component, and the complexity of handling visibility changes at the component level.

#

The main problem with replicating resources is conceptual. Resources often have behavior, which means complex internal state. Entities on the other hand are data-only and behavior is mainly inside systems. If you have a singleton data-only structure, that’s typically handled as a singleton entity (since that way you can access the data of the singleton directly).

#

So the question is, does replicating resources make sense architecturally? What kind of app structure do we facilitate with the replicon API (or do we just maximize utility?)?

spring raptor
#

I think replicating resources makes sense. They part of the world just like entities. In my game I have several data-only resources and I do prefer resources over singletone entities.
So just like with components if something implements Serialize or Deserialize or can be logically serialized with custom function - that we should allow replicating it.

neat orchid
#

Is RenetServer and RenetClient just state managers

#

They dont actually handle sending data but just manage what state the server / client are in

neat orchid
#

Is WebTransportServer a drop in replacement for UdpSocket or NetcodeServerTransit?

#

Wait should we use netcode at all with WebTransport or does WebTransport replace Netcode?
nevermind I believe I understand

sharp roost
#

So actually a lot of the complexity is still in Renet{Client, Server}

neat orchid
#

Ok I believe that I have a working version that has everything functioning however there is a weird issue with H3::server spaming "Sent datagram" and never actually doing anything? Also the code for webtransport seems a lot worse with no error handling. Also my MVP code is very hacky.

It is close to complete though.

neat orchid
neat orchid
#

At this point someone who knows Renet better would be ideal for debugging this because I believe there is an issue with renet

spring raptor
#

@echo lion about splitting entities and their components into message. We don't know component size ahead of time... Should we use intermediate buffer for entity and merge it with the packet buffer?

spring raptor
#

Better, I will write into a single buffer as we do now and just remember entity positions. And then feed slices equal or less of the packet size to Renet.

#

@willow osprey Remember I mentined memor-reuse API for transport lib? After some thinking, I don't think it worth it.
If I will write to special transport library buffer, I won't be able to reuse the message (avoid re-serializing components that didn't change) and it's more important.
So accepting a slice and copy under the hood is simpler and better. You can re-use this memory inside your lib.

willow osprey
#

so no big allocations, just a copy

willow osprey
#

partly because dealing with pooled buffers when the caller isn't necessarily able to tell you the length they want to write up front is a pain. and also i want to be efficient for small messages, since messages are the unit of replication, many of which get written to a single packet. i'm imagining writing all updates related to an entity in a single message. replicon could write to a temp buffer, then pass in the slice for that entity, which gets copied to a suitably sized small pooled buffer internally for writing to a packet

#

perhaps you have some idea about this btw - what's the largest message size you send for any project using replicon/renet, any ideas?

#

probably some initial game state when joining

#

i'm redesigning my ack header. it's a u32 bitfield atm but can only ack the prev 32msgs. so acks break if you send a message that fragments > 32 packets.

spring raptor
#

So your transport layer also handles this? If I pass multiple slices, they can end up in a single packet?

willow osprey
echo lion
spring raptor
#

What is the message ID for?

willow osprey
#

acks

#

message ids are handles to check when it was received

spring raptor
#

Cool!

#

When are you planning to release your library? :)

#

What transports will you support?

willow osprey
#

i still have a few fundamental changes to make to the format, i need to be able to pack in acks more efficiently, and bake in some checks so you can't accidentally have too many packets in flight and break the ack system

#

so dunno, week or two maybe.. it doesn't do any networking, just exchanges messages. so it needs to be bolted onto a transport. i might try getting it going with webrtc unreliables first

#

no concept of connections/auth/anything atm. literally just an endpoint you have to pass msgs to and from with another endpoint

willow osprey
#

i think as another crate

#

i'll release the messaging lib first tho for feedback

spring raptor
#

Got it, if you implement it and UDP transport, I will definitely consider switching.

willow osprey
#

yeah i should probably do udp first, that'll be easiest i guess

#

sneak preview

torpid patio
#

How much overhead do quic ipv6 packets have? Is like 400/1400 bytes useable

spring raptor
spring raptor
willow osprey
spring raptor
torpid patio
#

can every networked library be boiled down to reliable and or an unreliable channel that you can send and recieve bytes on

neat orchid
neat orchid
#

I believe the problem is my executor::block_on line which is causing an infinite break?

echo lion
willow osprey
spring raptor
spring raptor
willow osprey
spring raptor
#

Damn, such a good name.

haughty bobcat
#

I'm trying out replicon for my game, as my own networking implementation had very high overlap.
I got it working with just the player characters moving around, but I get a crash in the client when spawning my "minion" characters. (The minions follow the player around, based on a "Master(Entity)" component.)
I'm using the latest revision (23e579ed8c108cdc23d45ac854652f29cf0f70fd) on the main branch.

Master component:

#[derive(Component, Debug, Serialize, Deserialize)]
pub struct Master(pub Entity);

impl MapNetworkEntities for Master {
    fn map_entities<T: Mapper>(&mut self, mapper: &mut T) {
        self.0 = mapper.map(self.0);
    }
}

Added as replicated component on both client and server:

app.replicate_mapped::<Master>();

Spawning minions on the server:

world.spawn((
    Master(self.master_entity), // commenting this line prevents the crash
    Replication,
));

Client crashes with this error:

thread 'main' panicked at /rustc/f5dc2653fdd8b5d177b2ccbd84057954340a89fc\library\core\src\ops\function.rs:166:5:
called `Result::unwrap()` on an `Err` value: Io(Error { kind: UnexpectedEof, message: "failed to fill whole buffer" })
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in exclusive system `bevy_replicon::client::ClientPlugin::replication_receiving_system`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
error: process didn't exit successfully: `E:\Projects\hogmod2\target\release\client.exe` (exit code: 101)

@spring raptor Do you have a hunch on what the issue is, or ideas on what I can try? If not, I'll get to work on a minimum reproduction example.

spring raptor
# haughty bobcat I'm trying out replicon for my game, as my own networking implementation had ver...

Looks like a bug in replicon. failed to fill whole buffer means that on deserialization it tried to deserialize something, but it didn't get enought bits.
In replicon we serialize dynamically into a buffer to avoid extra allocation (we got about 2x speedup compared to feeding everything into a struct and serializing it).
It uses this module: https://github.com/lifescapegame/bevy_replicon/blob/master/src/server/replication_buffer.rs
Serialization happens here: https://github.com/lifescapegame/bevy_replicon/blob/23e579ed8c108cdc23d45ac854652f29cf0f70fd/src/server.rs#L153
Deserialization is here: https://github.com/lifescapegame/bevy_replicon/blob/23e579ed8c108cdc23d45ac854652f29cf0f70fd/src/client.rs#L56

If you will have troubles catching it, please submit a minimal to reproduce issue on GitHub, I will take a look after work.

GitHub

High level networking for the Bevy game engine. Contribute to lifescapegame/bevy_replicon development by creating an account on GitHub.

GitHub

High level networking for the Bevy game engine. Contribute to lifescapegame/bevy_replicon development by creating an account on GitHub.

GitHub

High level networking for the Bevy game engine. Contribute to lifescapegame/bevy_replicon development by creating an account on GitHub.

haughty bobcat
haughty bobcat
#

Right, my replicate::<T>() calls were not identical on the client and server.. 🙃

#

I have separate binaries for server and client, and did not realize it was critical for the replicate declarations to be the same

spring raptor
#

Replicon also works for scenario when client and server is the same binary, so it's also something to consider if it's fits your game.

haughty bobcat
#

I'm going for dedicated servers 🙂

#

Would it be a good idea to crash with a more helpful error message? I could look into it

#

Or some note in the docs

spring raptor
echo lion
spring raptor
#

Let's get back to it after I finish the current draft.

echo lion
#

Made an issue to track it

spring raptor
#

Almost finished. Only message acking is left to implement. And probably add tests for updates.

I wish we could have a built-in API for acks on transport level...

spring raptor
#

Manual serialization became quite complicated. Are there any good libraries for it?

spring raptor
echo lion
#

Ok will take a look this week

spring raptor
#

Take your time, it's a lot of changes.

viscid jacinth
#

hey i was wondering at which point do you apply sequencing (i.e. making sure we ignore older updates if we read a newer one)
Your deserialize functions take Tick as input but don't seem to use it

spring raptor
#

If you looking at the current master / release, we do it like this: server sends an update over unreliable channel since the last acknowledged tick and includes the tick in the message. And we discard the old ones by remembering the last applied tick.

#

But we sending a big single message which is not good - if message is bigger then the packet size, transport will split it into multiple packets. And if there is packet loss - it's more chances to lose the message.

#

This is where the linked PR above comes into play. I outlined how it works in it's description.
I think it's the best way of handling it.

spring raptor
spring raptor
#

@viscid jacinth I looked into your code and if I understand it correctly, you send an action per message.
I would highly recommend to avoid doing so because on packet loss / delays / reordering client can apply invalid state.

viscid jacinth
#

There's 2 things I don't get:

  • you send removes/inserts/updates in a single packet. But for example naia sends removes/inserts reliably, and updates unreliably which I think is preferable. But as you said sending everything related to an entity as a single packet is preferable.
  • Ok so things work for you because you only send a single message with the entire world state. What I wanted to do is this:
    • use a single SequencedUnreliable (sequenced channel=discard messages older than the most recent one) channel for entity-updates; however I could get into situation: we send two packets [E1] and [E2] for the same tick but the problem is that if we receive packet [E2] before [E1] we will discard [E1] because its message_id (1) is smaller than 2.
      My idea was to then use an unreliable channel and apply the sequencing separately for each entity.
spring raptor
# viscid jacinth There's 2 things I don't get: - you send removes/inserts/updates in a single pac...

Looks like I confused you, sorry. Let me elaborate.

you send removes/inserts/updates in a single packet.
This is what I did before. And it's no good, just a naive implementation that carried originally from my game.

But for example naia sends removes/inserts reliably, and updates unreliably
This is exactly what I do now in https://github.com/lifescapegame/bevy_replicon/pull/116
I just haven’t merged this PR yet because I’m waiting for the review from co-maintainer.

But as you said sending everything related to an entity as a single packet is preferable.
No, no, this is also not a good idea. Clients can still receive a component from one entity and miss a component for another related entity, which could result in an invalid state.
What I would recommend is to do exactly what naia does (and my crate now): send only updates (changes, not insertion) per entity.

And when I saying per entity I mean to split data per entity. I.e. pack updates into a message(s) up to 1200 bytes (max packet size), but split messages strictly per entity to avoid sending updates for a single entity partially (could cause weird behavior on packet loss).

viscid jacinth
#

I"m confused; you're saying I mean to split data per entity which is what I was proposing: send all updates for one entity into a single message.
But as you said the problem is that you could get:

  • all updates for entity E1 in packet P1
  • all updates for entity E2 in packet P2
    Packet P1 arrives way before P2 because of jitter, and now the client world is in an invalid state?

Or are you saying:

  • all entity actions (despawn/spawn/insert/remove) for ALL entities into a single message
  • the entity updates are sent with one message per entity; in which case you can't use a sequenced-channel, you need to use an unreliable channel and do the sequencing manually per component-entity pair (i.e. you remember the last received tick separately for each entity-component?)
spring raptor
# viscid jacinth I"m confused; you're saying `I mean to split data per entity` which is what I wa...

all entity actions (despawn/spawn/insert/remove) for ALL entities into a single message
Yes, for each tick we collect all these changes into a single message since the last tick and send over the reliable channel.

the entity updates are sent with one message per entity
One message per entity will waste space because each message should include tick and message number to let client detect if the update is old.
Instead we pack entities into messages up to max packet size (but don't split entities between messages, this is what I meant) with the current tick included.
These messages goes into unreliable channel.
Unlike messages for reliable channel, we collect component updates since the last acknowledged tick for each entity.
Clients remember last received tick per entity and when an update is applied, they compare the tick from the update with each applying entity tick.
These messages cannot be applied until the corresponding reliable message arrives (with despawns, insertions, and deletions), so they buffered if they arrived too early.
If there is no reliable message for this tick (i.e. no insertions, despawns removals on server), then the update message could be applied immediately.

I hope it's more clear now. English is not my native language. But it's basically what naia does.

If you want to build something like this, I would suggest to consider using replicon under the hood, it's quite extensible and will do a lot of things for you.

viscid jacinth
#

Collecting all entity actions in one message is an interesting idea to ensure that there's no invalid world state, I will have to think about it.
| Unlike messages for reliable channel, we collect component updates since the last acknowledged tick for each entity.

I'm not sure I fully get what this means.
But in that case, basically you get a buffer of entity updates, which you apply only when the component gets inserted? (For example you received the updates for tick 12 early, but later on you receive the insert for tick 10, at which point you can apply the tick 12 update for that component. The ACK-tick for that entity still says at tick 10 though?)

| If you want to build something like this, I would suggest to consider using replicon
aha well i'm building another networking library, so i just thought it could be useful to compare approaches

#

Thanks for your responses!

#

Also, your world state for tick T is valid in terms of archetypes (components/entities present), but might not be completely valid in terms of component values, because some entities might be on a different tick if they received some updates. Are you ok with that?

echo lion
#

#1090432346907492443 message and #1090432346907492443 message are relevant

viscid jacinth
#

Ah i see, groups of entities for which the updates+actions will be sent as a single message? could work, yes; especially for independent entities

echo lion
#

Ah yes, the same sync group and the same priority. A sync group can be 'desynced' if its members have different priorities and you get throttled.

viscid jacinth
#

Even with syncgroups; you would only get a consistent world state for those entities if actions+updates are sent as a single message. So you couldn't send only actions reliably, and updates in a separate unreliable message.

echo lion
#

Sync groups let you divide the world into sections that are small enough that they don't get fragmented (since fragmenting increases average latency).

viscid jacinth
#

ok but within sync-groups, you would then send all entity actions (spawn/despawn/insert/remove) and updates as a single message that is sent reliably, then

echo lion
#

Updates are sent unreliable, entity actions are reliable (and hopefully not fragmented).

viscid jacinth
#

But you said earlier that you would wait until you receive the reliable entity-actions to apply the update (so that the world is consistent). That's pretty much equivalent to sending everything as a single reliable message

#

Since in any case you have to wait for the reliable-message to arrive

echo lion
#

We can use a 'message reconstruction' optimization for fragmented updates: the client collects fragments until it can assemble a full message, and the server resends the same exact set of fragments until the relevant tick is acked or the message needs to be replaced due to an update. If components are not updated frequently, this works very well and lets you put everything in one sync group for certain kinds of games.

echo lion
#

Latency is tied to the reliable channel for entity actions, but we avoid resending stale component data by using 'custom refresh-reliability' .

echo lion
viscid jacinth
#

'custom refresh-reliability'? you mean resending any entity updates that are later than the last entity-actions that we sent?

echo lion
viscid jacinth
#

The ack is only for entity actions? or also for entity updates?
And it's sending diffs since last ack (instead of just the latest state) to have some compression on the message size?

echo lion
viscid jacinth
#

What is the point of separating actions ack vs updates ack?
And what would you do in this scenario?
You sent: actions at tick 13, actions at tick 14, updates at tick 15.
You receive the packets in order: 13, 15, 14.
13: apply the actions. Set action tick to 13.
15: apply the updates since they are later than the actions. Set update tick to 15
14: do you rollback the state to tick 14 so that the state for this group is consistently at tick 14? Or do you just apply the actions and set actions_tick = 14, updates_tick = 15?

echo lion
viscid jacinth
#

Ah so you send a message with 'empty-actions' every tick as well?

#

otherwise what happens if an entity gets updated for thousands of ticks without any actions

echo lion
#

As poffo said, small messages can be grouped together so presumably there is some amortization #1038137656107864084 message

viscid jacinth
#

Ok that might be possible; but let's say for example:

  • you send actions for tick 13
  • you send updates for tick 13 but it gets lost.
    Then if you apply the actions on tick 13, the world is not in a consistent state; in the sense that it's not similar to what the server world was at tick 13 (since the updates were lost).
    So actions OR updates are applied for tick T only if both are present for tick T?
echo lion
#

Maybe we could have an option to wait for actions and updates to synchronize?

spring raptor
# viscid jacinth Collecting all entity actions in one message is an interesting idea to ensure th...

For example you received the updates for tick 12 early, but later on you receive the insert for tick 10
Update gets buffered until you receive actions for tick 10 (until last tick on which world has insertion).

aha well i'm building another networking library, so i just thought it could be useful to compare approaches
I know. I just saying that it's quite a lot of work to reimplement replication from scratch.
I built replicon focused only on replication on purpose, many games including mine need only this functionality.
But I also made it extensible to let other people build crates on top for things like prediction. I like modularity.

Also, your world state for tick T is valid in terms of archetypes (components/entities present), but might not be completely valid in terms of component values
Correct, I had to sacrifice it to implement packet fragmentation.
I think it's relatively safe default, updates for entities are atomic, only different entities could have values for different ticks.
In the future I planning to add sync groups to connect related entities.

Ah so you send a message with 'empty-actions' every tick as well?
No, instead of sending an empty message with actions, I just include minimal required tick to apply for updates. This minimal tick is the last tick on which world have any insertions, removals or despawns. So if there are not actions this tick, update will simply reference older tick as required tick.
To clarify: update messages contain two ticks: minimal required tick and the current one to determine if an update is old or not.

Then if you apply the actions on tick 13, the world is not in a consistent state;
Yes, world will be different. Archetypes will be the same, but some entities could have older values.
I would say that it's fine... Like you have all players in the location, but some of their positions could differ from the ones on server.
@echo lion do you think it's critical?

echo lion
spring raptor
# echo lion I think it’s important to give people the option for full-world synchronization....

I think this will insanely complicate the current logic... Also users who use this option will have huge latency due to blocking.
When the connection is bad it's expected to have some partial teleports or see visual change apply partially.

I just asked naia dev and he confirmed my thought about it:
client should never assume that it's world state is the same as the server's on any given tick - world state on the client is only "eventually consistent" with the server's.

It seems that with any decision it is always a compromise. Without the PR we have 100% consistency, but more bandwidth use and higher probability of data loss. With this PR we send less and more resistent to packet loss, but only "eventual consistency".

echo lion
#

When I do my review I will think about the requirements.

grave yarrow
#

if not that could lead to some nasty bugs if not accounted for by users of the networking

spring raptor
# grave yarrow would the entities themselves be sent as a whole?

Sure, in both the current release and in the upcoming rework.

But in the upcoming rework we now no longer guarantee that on specific tick all entities will have the same values as it was on server. The state will be preserved only archetype-wise and per-entity, but some entities could be older.
But we still discuss if it's okay.

grave yarrow
#

gotcha

#

I think that is much more okay

crisp stump
viscid jacinth
#

Fair enough that it's not possible to have perfect world consistency if we don't send the entire world in one message.
One thing i'm wondering about though.
You choose to apply updates for tick 13 only if we received actions for tick 13.
However you can still get some different ticks per component: for example actions-13 (spawn C1) is received, and updates-13 (update C2) is late/lost, we apply actions-13 immediately, so component C1 is at tick 13, and component C2 is at tick 12.

In which case; isn't it strictly better to always apply updates (for components that exist) as soon as they arrive?
For example, we get updates-14 (component C1) first, and then actions-13 (spawn component C2). We immediately apply updates-14; so we get C1 at tick 14 and C2 at tick 13.

In both cases a single entity can have components at mismatched syncs, but in the second case updates are applied immediately, which might feel more responsive. Also the second case is much easier to implement (no need to have a buffer of waiting updates, no need to keep 2 ticks per entities)

viscid jacinth
viscid jacinth
#

Also, just to confirm my understanding; updates are sequenced, but actions are ordered (i.e. we apply every single actions)?

spring raptor
spring raptor
#

@echo lion remainder about the review :)

echo lion
spring raptor
#

@viscid jacinth If you like the described idea, consider using replicon inside lightyear under the hood instead of building one monolithic crate.
It will be easier to maintain and this way more developers will be involved. Scope of my crate is replication only.
Just a suggestion, though. Feel free to ask any questions about the implementation :)

viscid jacinth
#

thanks; but my goal was to try reimplementing replication from scratch so that I could understand everything (originally I was just using naia but I had some bugs in my game, and I couldn't understand the naia code so I decided to try doing it myself).
It is pretty hard to get right though; I don't think I could have come up with a working design without your help!

Your current design seems correct to me; one optimization could be to send all updates since the most recent of:

  • last ack-ed updates
  • last actions message sent
    instead of all the upates since the last-acked updates. No?
spring raptor
viscid jacinth
#

Right now you send all updates since last ACK-ed update.
For example if you send U-1, A-2, U-3; U-3 would be containing all the updates since tick 1.
But it's sufficient to only include all the updates since tick 2 actually, since we know that U-3 will only be applied after A-2 gets applied

spring raptor
# viscid jacinth Right now you send all updates since last ACK-ed update. For example if you send...

No, no, it's not like this. I tried to explain it here: #1090432346907492443 message.
When I detect any insertion on an entity, I include all components since the last acked tick and bump this last acked tick.
So in the provided example, U-3 will only include updates (if any) happened on tick 3 because last acked tick for this entity will be 2.

Discord

Discord is the easiest way to communicate over voice, video, and text. Chat, hang out, and stay close with your friends and communities.

#

To clarify: you need packet-fragmentation branch, not the current master.

viscid jacinth
#

Yes I understand that when there is an Action, you put the updates in the same message.

You do seem to do the optimization here: https://github.com/lifescapegame/bevy_replicon/blob/6aeed9f02f523d5f633dfbaa941a2a034ac4a891/src/server.rs#L340-L340
That's equivalent to the optimization I was mentioning; on A-2, you bump the ACK-tick to 2, so U-3 will only contain the updates since tick 2. That's what I meant, only gather the updates since last-ACK-tick or last-ACTION-tick (whichever is most recent)

GitHub

High level networking for the Bevy game engine. Contribute to lifescapegame/bevy_replicon development by creating an account on GitHub.

spring raptor
echo lion
#

@spring raptor I think there is a bug on reconnect, I am getting Tried to send a message to invalid client spam from renet when I reconnect a client (and if there is another client who has been disconnected).

spring raptor
#

Most likely that the cached client list wasn't properly cleaned.
But this part was reworked in the recent change.

echo lion
#

Ok I will test it. Have been slamming my head on reconnects for two days, feel like I am barely making any progress... renet has a frustrating issue where you can only reuse connect tokens with the exact same IP/port combo.

spring raptor
echo lion
#

I might have to spend part of this month forking renetcode to fix the connect token issue

spring raptor
#

I found the renet code quite good, shouldn't be hard to dive in.

#

@echo lion curious, what game are you developing?

echo lion
echo lion
spring raptor
#

Sounds interesting :)

echo lion
spring raptor
echo lion
echo lion
#

@spring raptor My first attempt with the branch didn't work: bevy_girk unit tests fail, bevy_girk_demo isn't replicating things properly. Will start to review the PR now. EDIT: oh no 1k diffs lol

spring raptor
echo lion
spring raptor
#

Thanks, I will take a look after work!

spring raptor
#

Looking into suggestions. You are so good at writing good explanations :)

spring raptor
#

Funny discovery, looks like in Rust you can't write like this:

while cursor.position() as usize < len {}

Braces are required:

while (cursor.position() as usize) < len {}

But the following works:

while cursor.position() < len as u64 {}
spring raptor
#

Fixed typos, sorry

spring raptor
#

@echo lion applied the suggestions, except these two:

GitHub

This PR implements the following logic:

Collect all mappings, insertions, removals and despawns into so-called init message and send it over reliable channel. This message contains changes only fo...

#

I don't want to include entity length in bytes, it will increase the package size and will require special logic for replication buffer.
But I can't apply the update either, it could contain older changes.

echo lion
#

Yeah it's tough... the only alternative I can think of is somehow detecting if an entire update message should be applied or not.

echo lion
#

@spring raptor that commit broke the PR lol, it needs to be debug_assert!(cursor.position() < end_pos ....

spring raptor
#

Damn, sorry :)

echo lion
spring raptor
#

About the entity data size. Another option:
We could deserialize it as usual, but pass something like drop: true into deserialization functions.
And if it's set to true, then use drop the value. Could be optimized if the size is know ahead of time, but still ugly, will continue thinking...

echo lion
spring raptor
#

Actually, I'm not event sure what is worse. I would say it's equally bad 🥲

Full deserialization with insertion into a world takes 60.222 µs for 900 entities with a single component.
I bet it will take much more for components that require heap allocation (need to benchmark), but it saves the bandwidth...

Including size will increase the message size significantly. And worse part is that we can't use varints here, so we will have to limit amount of data per entity which is not good. But will save on extra deserialization.

echo lion
spring raptor
#

I think it's reasonable size, but for the mentioned test it will be almost 1kb of the extra data....

#

Maybe yes, it's less worse.

echo lion
#

Sorry did not get a partial review done today (distracted by some UI stuff), will need to finish tomorrow.

spring raptor
#

@echo lion Can't come up with a better solution then entity length. Let's do it for now, maybe we will come up with something better later. u16 should be reasonable for update size?

echo lion
spring raptor
echo lion
#

Ok

echo lion
#

@spring raptor For serializing entity data, rather than serialize into each client buffer it might be more efficient to serialize once into an internal buffer, then copy onto the end of each client buffer.

spring raptor
#

I will play with it after the PR.

echo lion
#

That would let you store the entity data length as a varint (using VarintWriter), although varints are suboptimal if you can guarantee all entity updates are under 256 bytes.

echo lion
#

@spring raptor ok part 2 done.

echo lion
spring raptor
#

Thanks, I will try to address everything today-tomorrow

echo lion
#

@spring raptor another optimization would be to shove update messages into the init message if there is space.

spring raptor
#

Sounds awesome, yes

#

Actually... If it's lost, we don't want to resend it

echo lion
#

Ah right, it’s a bandwidth issue

spring raptor
#

Applied part of suggestions, working on caching entities right now.

echo lion
#

Review is done 😄

spring raptor
#

Great, thank you! I will try to address all suggestions tomorrow (going to sleep now).

If you would like to speedup the process, it would be great if you take a look at cursor advance on client and removed component events. You can open PRs to my branch.
If not - it's okay too :)

echo lion
#

I can do the removed component events one, maybe the client cursor issue. My todo list is perpetually overflowing 😭

echo lion
#

Ok found easy solution for cursor issue

spring raptor
#

Thanks, I will take a look soon!
You already helped a lot!

spring raptor
#

@echo lion addressed every suggestion. I delayed a few of them by opening issues (they are great, but I believe they out of the PR's scope).
Please take a look the fixes and at conversations that I left "unresolved" and commented.

spring raptor
#

Prefer to press "resolve converstaion" if you agree because it's the only way for me to differentiate addressed and non-addressed issues in PRs like this.

echo lion
#

I unresolved the ones that I need to check

spring raptor
#

Feel free to do this as well :)

#

@willow osprey how much acks do you hold?
I.e. how much older message client can acknowledge?

Since renet lacks acks API, I need to implement something like this myself for the unreliable replication channel.

spring raptor
# spring raptor Feel free to do this as well :)

Suggestion for the future.
When I agree and just apply the changes immediately without any comment - I will resolve the coversation.
If we discuss something and one of us agrees, he presses the button.
Will be easier to track things.

echo lion
#

Sure

echo lion
willow osprey
# spring raptor <@140137915270430720> how much acks do you hold? I.e. how much older message cli...

at the mo i have const that defines the max bytes allowed when writing the ack bitfield, to limit the size of the ack header in the packet. this also limits the number of un-acked in-flight packets, since clients won't send more packets unless there is capacity for them to be acked without losing any acks. currently i allow up to 50 bytes of ack header, which is 350 acks. but those are acks for packets, not messages. so with small messages that could potentially be thousands of messages ids that get acked. (since packet acks map to message ids for acking) https://github.com/RJ/pickleback/blob/main/src/protocol/ack_header.rs#L8

#

could probably get by with far fewer bytes allocated for acks tbh, depends on the volume of data being sent in bursts. larger number of acks allows for blasting out lots of packets at once when doing the initial state transfer

#

since for large fragmented messages, like the initial state transfer when joining a game, i send all the fragments (up to the max that can be acked) in one go, then continue with more+retransmission as needed when acks start arriving

echo lion
#

Also bevy_girk unit tests are still failing so I am debugging those

spring raptor
echo lion
#

We both want to use the resolve button to track things xd, maybe I should just make a separate list?

spring raptor
echo lion
#

Ok fixed my bevy_girk bug, I was consuming renet server events directly instead of using EventReader, which caused replicon to not see the events (so no client ever got connected).

#

Yay!! Reconnecting now mostly works XD

spring raptor
#

@echo lion how about the following system for cleaning acks:
if last ack index is older then the first one by 350, then drop it?

willow osprey
#

does renet support infinite acks? i think it has an ack packet type iirc..

#

yeah i think it will just create an enormous packet of necessary ack ranges. not sure if that can then be fragmented.

spring raptor
# willow osprey does renet support infinite acks? i think it has an ack packet type iirc..

Previosly we didn't care about old acks. Clients sent acks over unreliable channel and on server we just bumped the last acknowledged tick for each client.
But in this new PR we need normal acks for unreliable channel and right now we acknowledge any old acks without limit. And thinking about the solution, looks like we need something like what transport libraries do.

spring raptor
willow osprey
#

renet doesn't seem to have a limit the number of acks it can send, so could in theory ack very old packets i think

spring raptor
#

But if client will throttle old acks on purpose, he can create a memory leak on server.

#

Because we remember what entities was present on each packet.

willow osprey
#

i see

spring raptor
#

And we thinking about dropping too old acks.

#

It's sad that renet doesn't expose this functionality. We could use Renet's acks instead.

#

If I understand correctly, your library returns a handle when I send a message?

willow osprey
#

yeah a fairly small window should be fine for replicon anyway i think? since you resend entity spawn etc messages on the reliable channel, and regularly send updates since last acknowledged tick over unreliable, so seems fine to keep a limited window. i haven't properly examined your new changes though

#

yes i return a msg handle, and that gets acked when the packet it was on is acked. i'm putting acks into all packet headers, which is why i limit it. need room for the payload

spring raptor
willow osprey
#

are you talking about acks for packets, or for entities, or what

spring raptor
#

acks for messages that contains entities and their updates :)

#

Each message = packet size

willow osprey
#

ok. if you have 1000 entities moving and server sends updates at 60hz, and each entity needs ?20bytes, that's ~20 packets per tick, or 1200 packets s->c per second. so 350 packets is a third of a second worth of data

spring raptor
#

Yes, it's kinda depends on entities and their sizes...

willow osprey
#

memory fairly cheap anyway right, you could probably store a lot more

spring raptor
#

Yep

spring raptor
#

Maybe dropping all data for messages that wasn't acknowledged for 10 seconds should be good to go?

echo lion
#

Instead of 10seconds, you can do the server timeout interval * 2 (which is 10s by default but can change).

spring raptor
#

Addressed suggestions, but still need to add the acks cleanup. Going to sleep right now.
If you want to speedup the process, feel free to play with it. If not - it's totally fine, I will implement it tomorrow.

echo lion
#

I think we can do acks cleanup in a separate PR. I will open an issue to track it.

spring raptor
#

Okay!

Then take a look at other conversations and mark as "resolved" or add a comment, I will take a look tomorrow after I wake up :)

spring raptor
#

I was still getting ready for bed, so I saw that you approved and I merged.
I feel like I will have a good sleep tonight 😅

Thank you, I like your reviews, it helps a lot.
It's not easy to dive deep into someone else's changes, especially if it's ~1k diff, truly heroic job 😄

spring raptor
# echo lion Instead of 10seconds, you can do the server timeout interval * 2 (which is 10s b...

Looks like this struct is related to transport (netcode):
https://docs.rs/renet/0.0.14/renet/transport/struct.ConnectToken.html#structfield.timeout_seconds
We need to avoid dependencies on it to be transport-independent.

sharp roost
#

If someone has missed 10 seconds of updates isn't it best to just resync everything anyway?

spring raptor
#

We trying to solve possible attack vector for clients that don't ack specific updates on purpose (server keeps some meta info for unacked updates).

The idea is to drop such acks and their info if client didn't send ack for it for 10s.

spring raptor
#

I mostly don't like it because of how I interract with buffers... I have to think how to abstract it better.

spring raptor
#

@echo lion updated the PR.
When you approve it, I will draft a new release.
Often releases are better for users.

spring raptor
#

Oh, you did a review, I somehow missed it

#

Applied, nice catch with underflow!

spring raptor
echo lion
woeful saffron
#

Hello, after updating to 0.18 I get this error:
bevy_replicon-0.18.0\src\server.rs:439:18: entity should be present after adding component
I'm not certain what that implies. Did an entity get despawned after a component was added?

woeful saffron
echo lion
#

@spring raptor

spring raptor
#

Could you send me a minimal project to reproduce?

#

@woeful saffron do you have components that reference other entities?

#

I would suggest to check if all of them implement mapping.

echo lion
spring raptor
#

Right...

#

Then never mind, try to get something minimally reproducible and I will debug it.

spring raptor
#

Thank you!
I think I know why it fails. @echo lion we probably should handle this case, right?

echo lion
#

Hmm yeah

spring raptor
#

I will take a look tomorrow, going to sleep right now.

echo lion
#

So the is_added needs to use max(component change tick .last_run(), replication component change tick .last_run()). But I wonder about the perf effects.

spring raptor
#

Yep, I will benchmark of course.

spring raptor
#

Why? We indeed need to take into account time when Replication component was inserted.

#

Oh, yes, no max

#

Just check if it was inserted in this tick

#

And if it does, replicate everything.

#

I will think about how to make it nice and performant.

#

Hard to tell without looking into the code :)

echo lion
spring raptor
#

Yes, yes

#

@woeful saffron in the meanwhile just downgrade to the previous version, I will try to release a patch tomorrow. Thanks!

The newly added mechanism is more performant, but also much trickier to get right. We tracked a lot of edge cases before the release, but missed this one 😅

woeful saffron
spring raptor
echo lion
#

Mainly bandwidth for users with high-frequency component updates, and users with large numbers of mutating entities.

#

We should get a nice CPU perf win from using a shared serialization buffer, which is todo

spring raptor
echo lion
#

Hmm would we be able to use archetype or entity-level change detection when rooms are implemented? Maybe by caching a hashmap of room ids per entity in an archetype then if the archetype has not changed check for client room entry/leaving. But at that point I wonder if we gained anything.

#

Or maybe we iterate all entities but if no room changes on an entity + archetype did not change then don’t iterate components. Small optimization.

#

The trade off is between managing a hashmap (maybe expensive) and iterating all entities and doing per-entity room checks (more work that checking the hashmap), in the case of archetype nonchange.

spring raptor
muted swan
#

hi, replicon is working with renet, right?
renet recently added support for steam network iirc

can i already use steam with replicon?
and is it possible that i can easily switch between both without any/much code change?

spring raptor
muted swan
#

ah thats great to hear, thank you!

#

looking forward to it :)

spring raptor
woeful saffron
near glacier
#

Hello I have a question.
Bevy replicon replicates component states every frame from what i understand, in my use case i want to synchronize spawns and not synchronize everything every frame, is there a way to do that?

echo lion
#

Spawns are always synchronized, although component updates are only eventually consistent. If you want to control when the replication system runs you can do TickPolicy::Manual (although if you do this then unacked component updates will not be sent until you run the system again).

spring raptor
#

@woeful saffron Published a release with the fix.

echo lion
#

For #109 I wonder if we could do TickPolicy::Manual + add a system for resending the last component updates until acked (which is also TickPolicy::Manual).

spring raptor
#

I mean already.

spring raptor
echo lion
spring raptor
#

I.e. reusing old messages?

echo lion
#

Right, and scanning for changes

spring raptor
#

Let's think about it after the upcoming buffer rework.

#

I will see what I can do about it.

near glacier
#

In TickPolicy::Manual how does one manually run a network send tick?

echo lion
#

Since renet uses Bytes, we can just store a copy per client. Very cheap, no need to allocate again.

spring raptor
#

And it will trigged sending data.

near glacier
#

Thank you

spring raptor
#

In Manual. I don't think that ServerSet::Send should be manually configured.

echo lion
#

I can update it

spring raptor
#

It was the case before, but now we need to just ask user to increment RepliconTick.

#

Thanks, you are really good at writing docs/comments 😅

spring raptor
spring raptor
#

@echo lion answering here since it replicon-specific.
You are free to try, but I a bit skeptical about option 4...
The mentioned solution sounds quite complex, makes pre-mapped entities less ergonomic (I found the current API more obvious) and involves insertions / removals which will affect performance due to archetype movements.
I would suggest to try to evaluate option 2, maybe we could make it ergonomic. And it's much easier to make PoC for it

#

Will be a bit busy these weekends, unfortunately, can't suggest much.

echo lion
#

Pre-mapped entities shouldn't cause additional archetype moves, since you can add the ClientMapped and Premapped components when spawning the entities.

spring raptor
#

Oh, I thought that you remove Premapped later.

#

My bad

#

But why two components then?

echo lion
#

Hmm don't think that's needed. Replication will be added like normal, which is one move

echo lion
spring raptor
#

Got it, makes sense

near glacier
#

I'm getting a panic "tick should be inserted on any component insertion", which originates from bevy_replicon::server::ServerPlugin::acks_receiving_system.
Have no idea how to fix or what I'm doing wrong any suggestion would be appreciated

spring raptor
near glacier
#

It still happens, I think it happens when you spawn and despawn a replicated entity before its replicated on clients

spring raptor
#

Damn, while the concept implemented in 0.18.0 is awesome, it also have a lot of edge cases 😅

spring raptor
#

I will take a look.

near glacier
#

I will try to make one asap, probably tomorrow

spring raptor
#

Will try writing test for it today.

near glacier
#

Ok thank you so much :))

spring raptor
# near glacier It still happens, I think it happens when you spawn and despawn a replicated ent...

No, can't reproduce, the following test pass:

#[test]
fn despawn_before_replication() {
    let mut server_app = App::new();
    let mut client_app = App::new();
    for app in [&mut server_app, &mut client_app] {
        app.add_plugins((
            MinimalPlugins,
            ReplicationPlugins.set(ServerPlugin {
                tick_policy: TickPolicy::EveryFrame,
                ..Default::default()
            }),
        ))
        .replicate::<TableComponent>();
    }

    common::connect(&mut server_app, &mut client_app);

    let server_entity = server_app.world.spawn((Replication, TableComponent)).id();

    server_app.update();

    server_app.world.entity_mut(server_entity).despawn();

    server_app.update();
    client_app.update();
}
spring raptor
#

@near glacier any chance you could provide a minimal example to reproduce the problem? Not in a test form, just a minimal that project that I can debug.

#

@echo lion thinking about entities, with the proposed solution using components instead of entity map, how we will map entities inside components? Right now I just pass entity map to the trait.

echo lion
spring raptor
#

Oh, never mind, for some reason I thought that regular entity mapping will also be replaced, sorry.

spring raptor
#

@echo lion one more thing to consider. If client disconnected and reconnected, maybe just despawn all pre-mapped entities that didn't acknowledged by the server?
Just like with events - if an event gets missed due to reconnect cycle, we don't do anything.

echo lion
#

Right that's the idea. But we need to be careful to only despawn entities that we weren't sent (or were sent to a previous session). An entity sent between 'just connected' and the arrival of the first init message may have been received but not yet replicated.

spring raptor
echo lion
#

Hmm it might work, I will think about it

spring raptor
#

Also if we despawn any pre-spawned entity, then user still have to deal with removals/despawns/inserts/spawns caused by reconnect...
I.e. despite we repair client, we won't have 100% persistent entities as it would without disconnect.
So I afraid it won't solve the problem entirely.

echo lion
#

However yes, I agree that since pre-mapping is expected to fail in case of disconnect, we can assume the user has a plan for failures and so we can despawn all premaps on reconnect (for best continuity, despawn them right before the first init message is processed).

spring raptor
# echo lion Hmm it might work, I will think about it

Let me know, I like this approach for pre-mapping a bit more. And I think having a list of non-acked pre-mapped entities is just useful in general, not only for repair.
The idea is to insert pre-mapped entities into a resource on client that will automatically send pre-mappings to server instead of using user events for it (current master approach). When a mapping is received, we remove it from the resource.

near glacier
spring raptor
echo lion
#

I doubt there is a good way to replicate prespawns from client to server. The server needs its own systems for properly validating and applying client inputs within its game protocol.

spring raptor
echo lion
#

Events are better for game server protocols since you get precise execution flow.

spring raptor
echo lion
#

Oh I think I see what you’re getting at - how to let the user know if a premapped client entity failed to spawn on the server in normal play (not just after a reconnect).

spring raptor
#

But the current approach (user defined events) are more ergonomic and have precise execution flow....

spring raptor
#

Will debug

echo lion
# echo lion Oh I think I see what you’re getting at - how to let the user know if a premappe...

This is a bigger problem in general - how to track the status of request/response patterns. I solved this in bevy_simplenet with a lot of synchronization and tracking complexity (and there is still one race condition to fix...). A proper solution with renet/replicon would require access to renet acks, plus carefully-crafted tracking code. My current plan is this somewhat clumsy protocol (that would not work for FPS/etc. games): https://github.com/UkoeHB/bevy_girk/issues/1.

sharp roost
#

And then when resimulating that entitie will either be spawned again, or it won't (in which case it was mispredicted anyway) or it will have been spawned on the server and been replicated properly, in which case it's no longer predicted, it's just a real entity

echo lion
sharp roost
#

So let's say the player can spawn a block when they click on the screen, abd the server is 2 ticks behind this client
Then it goes:

C 2: press button & spawn block | S 0: <normal play>
C 3: get server state 0 and maybe replay tick 1&2 | S 1: get input for tick 2
C 4: <same> | S 2: client pressed button, spawn block
C 5: get server state 2 and server spawned an entity, so resimulate from tick 2, while removing the local block | S 3: <normal play>
#

So basically when the client runs a system for spawning something that to-be-replicated entity has the Replicate component (same as in replicon), and so whenever the client has any entity with that tag you know it has just predicted the spawning of an entity that will eventually come from the server

spring raptor
# echo lion This is a bigger problem in general - how to track the status of request/respons...

Sorry, I don't think I follow.
It's quite a busy week, so forgive If I understand slowly.

Let's start from the beginning.
You want to handle reconnects and you want to keep already spawned entities to avoid writing cleanup logic.
You suggested a non-intrusive way, but in order to implement it you need to rework pre-mappings.
You proposed to use 2 components (Premapped for client to register such entity and ClientMapped on server to send it later on connect/reconnect) instead of entity map to avoid pre-mappings getting lost.
I suggested an alternative - we can use only Premapped, this way if server received a mapping, but client lose it - an entity will be despawned. And I think you confirmed that it's okay or am I misunderstood you?
Then I tried to extend it. What if instead of Premapped use a resource on client? This way users will have access to which entities has been acked by server. I think this approach would be a little more convenient then Premapped component. What do you think about it?

spring raptor
echo lion
spring raptor
#

I think I just get confused. Tough week.

#

Let me re-read what you write in the issue once again.

#

I think I get it now.

#

I'm so sorry.

#

I like what you suggesting.

echo lion
# spring raptor Sorry, I don't think I follow. It's quite a busy week, so forgive If I understan...

Mostly right. The Premapped component is only for tagging entities, you still use client events to send the info. When there is a reconnect you can detect which prespawned entities failed to spawn on the server by seeing which ones with Premapped on the client don’t have Replication after the reconnect. However, if there WASNT a reconnect, how do you know if a premap failed? Right now the only way is a timer on the entity that auto-despawns after some time without being replicated.

spring raptor
echo lion
#

It helps with automated cleanup, that’s all

spring raptor
#

But you mentioned a few edge cases, right?

#

Oh, I think you described how to handle them (re-reading the issue).

#

@echo lion one more question. Will you be fine if we rework mapping just as you suggested, but include RepliconClientRepairPlugin into a separate crate?
It's not something that all games need, but something that could be useful for the type of games like you making (without reset logic).

#

I will mention the crate in the readme, of course.

spring raptor
# echo lion Yes that works for me 🙂

Cool, feel free to open a PR with the proposed redesign of pre-mappings.

I'm tracking the mentioned above despawn bug. So odd, I register a despawn on server, write it to the buffer, but it's gets missed on client for some reason.

#

It happens when user spawn new entities every frame and despawn every entity every ms. Quite a nice streess-test 😅

echo lion
#

Does it actually get spawned on the client? So an entity leak?

#

All spawning/despawning is in Update?

spring raptor
echo lion
#

Looked at the example code, seems normal to me

spring raptor
#

Yep, but you run server and client, hold space for a few secs, you will see a leaked entity on client.

echo lion
#

Could be entity serialization is wrong if generation is not zero?

spring raptor
#

Thought about it, but looks like entities have correct deserialized generation. It just sometimes gets missed.

#

Like messages just get wrongly ordered

#

Will try to confirm it.

#

No, no reordering. The despawn even itself gets missed by Bevy.

#

Here is the log of my iteration over RemovedComponents<Replication>:

2023-12-26T20:27:13.909095Z TRACE bevy_replicon::server: removed 4v7
2023-12-26T20:27:13.909126Z TRACE bevy_replicon::server: removed 3v7
2023-12-26T20:27:13.941854Z TRACE bevy_replicon::server: removed 4v8
2023-12-26T20:27:13.941874Z TRACE bevy_replicon::server: removed 3v8
2023-12-26T20:27:13.975436Z TRACE bevy_replicon::server: removed 4v9
2023-12-26T20:27:13.975465Z TRACE bevy_replicon::server: removed 3v9
2023-12-26T20:27:13.992645Z TRACE bevy_replicon::server: removed 4v10
2023-12-26T20:27:14.042486Z TRACE bevy_replicon::server: removed 4v11
2023-12-26T20:27:14.042504Z TRACE bevy_replicon::server: removed 3v11
2023-12-26T20:27:14.059621Z TRACE bevy_replicon::server: removed 4v12
2023-12-26T20:27:14.092947Z TRACE bevy_replicon::server: removed 3v12
2023-12-26T20:27:14.092976Z TRACE bevy_replicon::server: removed 4v13
2023-12-26T20:27:14.126037Z TRACE bevy_replicon::server: removed 3v13

3v10 just missed in the log.
(I was wrong before when I said that it gets written into the buffer, it's not, I probably misread the id)

#

Will try to make an example without replicon.

sharp roost
#

If there's any components that reference that Entity then they'll also be removed before resimulating (because the state of all entities are rolled back), so it doesn't risk any references to predicted entities becoming stale just because they get despawned regularly

spring raptor
#

Then I misunderstand you, we don't do it like this.

#

But you will screw Bevy change detection this way.

echo lion
#

Ah I get it, do you have some partial client-side authority? Or interpolation between predicted and rerolled state?

sharp roost
#

Yeah there's probably some issues I'm overlooking, but it works as a proof of concept so far at least

spring raptor
#

Yes, good question, how you interpolate a predicted entity if you don't know to which entity it belongs?

spring raptor
#

It could be okay for some games, but it could really mess user logic that relies on it.

sharp roost
#

I don't have any client-side authority (the input gets sent through a separate channel) and I haven't implemented any form of interpolation yet, so other players are probably pretty choppy

#

That's a good point, though I'm not sure if that's even solvable when doing resimulation 🤔

echo lion
#

Your own character will rubber-band without interpolation + other techniques (like delaying predicted input based on ping).

spring raptor
#

bevy_timewarp doesn't despawn entities.
Instead it just rollback components with defined interpolation.
And it uses the mentioned pre-mapping feature to avoid despawning predicted entities.

sharp roost
#

I don't actually understand how pre-mapping works

sharp roost
spring raptor
#

Let me try to explain.
You spawn an entity on client and send a user-defined message to server with something like "Hey, I spawned a rocket with <ClientEntityID>". Then if user want to confirm this action on server, it spawns an entity and inserts a mapping ServerEntity->ClientEntity into a a special map from replicon (planning to switch to components, but it's another story).
Then when client receives a message, it processes such mappings first and all replication will be applied to the predicted entity.

spring raptor
echo lion
#

Yeah it would be handy to have a transport adaptor for controlling latency/packet loss/jitter programatically.

spring raptor
sharp roost
#

I'm a bit confused about how replicon knows which entity on the client matches any given entity spawned on the server. Like let's say you predict-spawn 3 different entities on a client, and the server then spawns 3 entities and send them to the client, how do you know which entity on the server matches which exact entity on the client? Does it match the component values or archetypes?

spring raptor
# sharp roost I'm a bit confused about how replicon knows which entity on the client matches a...

In replicon clients have a map which maps server entities into client entities. Even for non-predicted entities. When you receive a replication message, it contains server entity IDs and their new data. And client maps the received IDs into its own IDs (by spawning missing ones and adding them into the map).

Now let's back to predicted entities. Imagine that you send SpawnRocket(Entity) message to server 3 times. On server user should process these messages, spwan 3 rockets and insert their IDs (server->client) into a replicon's map on server. When sending replicaiton message, server will include these mapping into the replication message.
And then client will automatically just add the received mappings into its own replicon's map before processing all other data. Then replication continues as usual. Predicted entities now are regular entities.

spring raptor
echo lion
spring raptor
echo lion
# sharp roost I'm a bit confused about how replicon knows which entity on the client matches a...

You send an event to the server (manually) that contains the client entity. Then on the server you spawn an entity and insert the { server entity, client entity } mapping into replicon. Then replicon-server will send that mapping to replicon-client, and replicon-client will save that mapping in its internal { server entity : client entity } map so that further server updates will be written to the original client entity.

echo lion
#

Maybe we can continue using the removal reader, but run it every tick to drain events into the internal entity_buffer

spring raptor
crisp stump
#

i barely understand what u guys talk about here but thank u for making replicon better :D

sharp roost
echo lion
spring raptor
# sharp roost So it relies on the order of the events being the same as the order of spawning ...

Order of events is unrelated.

Let's start with mappings in general. When client receives an update, it needs to know which server entity corresponds to which local entity. So replicon automatically creates a map of server entities <-> local entities. Is this part clear?
But for pre-mapping we can't detect it automatically, we need server to send us mapping back. This is why on server we ask user to register this mapping, it will be later send to client with the rest of the replication data.

sharp roost
#

So for non-predicted spawns you just spawn the entities on the client and then map the server entity to the newly created entity on the client. But let's say you predict-spawn two balls in position A and B, lets call them c1 and c2 respectively, so the client sends two SpawnBall events to the server and that's all good, then when the server spawns those two balls, because of entity ordering shenanigans it spawns the first ball in position B and the second in position A, let's call them s1 and s2 respectively. In this scenario s1 should map to c2, and s2 to c1, but how does it know? Does it compare the transforms and other components?

spring raptor
sharp roost
#

I'm just confused about how you know which exact entity on the client matches which on the server, since it seems like there should be a lot of ambiguity in that

#

I'll try reading the code for timewarp and see if that makes sense, thanks for helping me understand!

spring raptor
spring raptor
echo lion
spring raptor
woeful saffron
echo lion
#

If you do TickPolicy::EveryFrame in the server plugin it should work until we can fix it properly

woeful saffron
echo lion
#

Ok it might be another bug then...

spring raptor
# woeful saffron hmm, this did not fix it for me

Interesting, in the reproducible example switching to TickPolicy::EveryFrame fixed the problem...
Let me fix the mentioned problem first (going to do it right now) and then I will ask you to create a reproducible example based on the latest master.

Damn, previous approach was so bulletproof, this new is so much trickier to get right even with 92% test coverage 😢

echo lion
spring raptor
echo lion
#

yep checking now

spring raptor
echo lion
#

Needs changelog entry

#

Yeah I personally dislike resource_scope, the indentation is ugly

spring raptor
#

BTW, why use Prespawn components and then cache them? For faster iteration?

echo lion
spring raptor
#

Got it!

#

@woeful saffron could you try to reproduce the issue using the latest master?
I just merged a fix, but I'm not sure if your problem is related.

spring raptor
#

I will draft a new release now and if your bug is still in place, I will draft a new release again right after the fix.

spring raptor
echo lion
#

@spring raptor I think there is a race condition, the tests sending_receiving and mapping_and_sending_receiving just failed for me locally, and then succeeded on a rerun (either that or the client/server connection is racy in tests).

#

Yeah a number of other tests also fail if I rerun them a few times.

spring raptor
echo lion
#

It might be the same thing with benchmarks where MacOS can have latency over localhost.

spring raptor
#

Right, most likely it. I will do a stress test on my machine soon.

spring raptor
#

@echo lion quick question.
Is it mandatory to reset resources for you? Maybe it would be more convenient to just put a condition on a set?
In case we add more cleanup logic you won't need to change your code.

echo lion
#

You have to do different things with each resource

spring raptor
# echo lion You have to do different things with each resource

You mean different then in ClientPlugin::reset?
I assumed that you need to manually do a cleanup when you end a session and don't want to reconnect or is it for something different? I.e. the exported ServerEntityMap::clear and BufferedUpdates::clear.

echo lion
#

Buffered updates need to be cleared, but the entity and tick maps are cleaned manually, you can check the repair plugin.

spring raptor
#

Got it!

echo lion
#

@spring raptor hmm, if you have a replicated entity with no components (other than Replication) then it won't be sent. Maybe this is the source of the bug? Spawn with no components, then add components in a later tick.

This is an edge case I'm not sure how to solve for the repair plugin. Suppose an entity is replicated with replicated components, then while disconnected the components are removed (but the entity isn't despawned). The reconnected client won't receive the entity since it has no contents, so the repair plugin will despawn it (even though components might be added back on the same server entity).

spring raptor
spring raptor
#

I have a better idea. Maybe consider this as a special case? We already have a check if an entity has just spawned. If so, then send even an entity with zero components since it's a spawn.

sharp roost
spring raptor
spring raptor
#

If I remember correctly, you are the author, right?

sharp roost
#

Yeahh

#

I guess that is possible yeah! I'll try to make that happen shortly then, I'm pretty sure it should be possible as-is even without a new release

#

I didn't realize that it's actually completely independent, but now that you mention it, it seems pretty obvious, I'll ask poffo what he wants to do with regards to naming and such

spring raptor
sharp roost
#

Yeah I was shocked by how easy it was to make the transport layer in the first place, it's a very nice code architecture

#

And yeah I know it requires some extra fixes to be able to use it properly, I juwt chased down some of those issues in my own code that's using the pre-PR version of the transport layer, and it's a lot better in the latest master

spring raptor
#

@sharp roost if you are planning to implement a high level networking crate, I would suggest to consider using replicon for replication part. I made it extensible with this in mind.
While replication may sound easy, it's actually quite tricky to get it right. Our implementation is also very efficient :)

sharp roost
#

Yeah I will use it in the future, but it turns out that its really fun! Mine is extremely inefficient, so it wouldn't be suitable for any actual games anyway

#

And luckily it spurred me to make a channel transport for testing, so it turned out to be a net positive 😄

spring raptor
#

Okay :)
Feel free to ask questions about the implementation if you curious how we did it.

echo lion
#

@spring raptor probable bug: what if you remove Ignored<T> from an entity after a few ticks?

spring raptor
spring raptor
echo lion
#

We probably need a Removed<Ignored> tracker or something... tough

spring raptor
echo lion
#

yeah

spring raptor
#

It's okay, I haven't started yet.

#

Just finished with my job.

#

Looking forward to the proposed solution.

echo lion
#

I will work on client prespawns this afternoon

spring raptor
# echo lion Ok done

Great, I thought to make it exactly this way.
I will push some minor style things just for my preference + adjust some tests (we no longer need to insert TableComponent in to trigger replication).

#

adjust some tests (we no longer need to insert TableComponent in to trigger replication).
Probably better in separate PR.

#

Approved

spring raptor
woeful saffron
# spring raptor <@267772636145254400> could you try to reproduce the issue using the latest mast...

Im on 0.18.2 and I still have the bug 😦

bevy_replicon-0.18.2\src\server\clients_info.rs:174:18:
tick should be inserted on any component insertion
stack backtrace:
note: Some details are omitted, run with RUST_BACKTRACE=full for a verbose backtrace.
Encountered a panic in system bevy_replicon::server::ServerPlugin::acks_receiving_system!
Encountered a panic in system bevy_app::main_schedule::Main::run_main!

#

I can try to create a reproducing example, next week at the earliest

spring raptor
spring raptor
echo lion
#

I got distracted working on an events API for bevy_simplenet (inspirational shower thoughts), will have to do the premapping refactor this weekend.

spring raptor
echo lion
#

There is also the problem of adding Ignored after a few ticks. The component will stop replicating but won’t be removed.

spring raptor
#

@echo lion what if we trigger change detection on Ignored<T> removal and detect removal of T when we add Ignored<T> via our buffers?

echo lion
#

Trigger change detection on T? Won’t the component be registered as an update and not insertion? It will also cause normal systems with Changed to go off which may be unexpected.

spring raptor
#

I thought that it's possible to trigger addition, but yeah, it's just awkward 😅

#

Then we need either something like IgnoreBuffer with removals and insertions of all Ignored<T> or a better API.

#

I personally never need dynamically enable and disable replication for specific component. It's usually an archetype thing.

#

@echo lion what if we make Ignored<T> private and provide a custom command to ignore a component on an entity?

commands.spawn((MyComponent, Replication, Transform)).not_replicate::<Transform>();

This way users won't be able to remove component.
It will still be possible to call something like commands.entity(entity).not_replicate::<Transform>(), need a way to detect it and trigger a panic.

echo lion
#

Maybe we could: A) detect when Ignored<T> is inserted and if T already exists (added in an earlier tick) then warn/panic, B) detect when Ignored<T> is removed and warn/panic.

spring raptor
echo lion
#

We could probably store a bool or int in the Ignored component to help track things, but that implies more data access during replication

spring raptor
#

Yes, it's better to forbid dynamic usage for now and see if anyone need anything more.

spring raptor
spring raptor
#

Found a workaround.

#

The following approach was also suggestion, but not sure about this one...
#ecs-dev message

echo lion
#

Your idea to detect Replication insertion (since the last time ServerSet::Send ran) seems most flexible.

#

It does make me nervous about edge conditions lol, maybe we need a fuzz test...

#

It might be good to write one big fuzz test for all of replicon and run it periodically.

spring raptor
#

Yes, it may be a good idea

spring raptor
candid eagle
#

Is it possible to have separate client and server plugins similar to how lightyear does it?

So for a headless server you'd add the server plugin
And for a client you'd add the client plugin
And for a client-server (think non dedicated host) you load both

#

for my use case a non-headless server is just a client that is also a server

candid eagle
#

lightyear doesn't support running the client and server plugins in the same process so my only option would be to run two separate instances of the game at once which isn't ideal

spring raptor
#

Sad to hear.

When you want server to also be a client we also support a pattern that alows you play locally without connection to himself.

#

I would suggest to consider this option too.
But your use case is also supporterd, @echo lion use it this way.

candid eagle
#

yeah, the big thing for me is i don't want to have to duplicate client stuff over to the server plugin with an if statement 😆

spring raptor
candid eagle
#

replicon doesn't support client side prediction on its own right? You need timewarp/snap?

spring raptor
# candid eagle replicon doesn't support client side prediction on its own right? You need timew...

Yes. Since my game don't need it, I decided to maintain only this part.
But I made it extensible, so it's possible to integrate client side prediction on top like the mentioned crates do.
bevy_timewarp is not actually a plugin for replicion, it's completely independent, but it's possible to use them together: https://github.com/RJ/bevy_timewarp/blob/main/REPLICON_INTEGRATION.md
I suggested the author to provide a more convenient "glue" to automate the mentioned integration (something like bevy_replicon_timewarp), but he is busy with other stuff right now.

candid eagle
#

am i right in thinking that replicon_snap isn't production ready?

spring raptor
candid eagle
spring raptor
spring raptor
#

@echo lion Looks like when I apply review suggestions from the browser it automatically resolves it.
So If you see a resolved conversation from me, it's an automatic system, I will not press "resolve" manually as we agreed.

grave yarrow
#

Is there any prioritization mechanisms for replication?

#

or is the entire world diff sent every tick?

echo lion
#

No prioritization yet

#

It’s on the roadmap but rooms are the next big feature

grave yarrow
#

gotcha

#

rooms being a partitioning per player or across servers?

echo lion
spring raptor
candid eagle
#

am i right in thinking that to use bevy_timewarp with bevy_replicon you need to manually implement custom deserialisers/serialisers for all the components you want to predict?

waxen barn
#

Hi there, how can I on the client get my own client id?

#

Actually, what I'm trying to do is have ownership, I need to know which character is mine, I was going to do this by comparing client ids but if there's a different way...

echo lion
echo lion
#

@rose patio let's continue here. What are you doing to set up the server and client? Can you confirm the client is actually connected?

rose patio
echo lion
rose patio
# echo lion Hmm can you share the code for your component struct? And can you double-check y...
#[derive(Component, Default, Deserialize, Serialize, Resource)]
pub struct TrialVec {
    pub trial: Vec<u8>,
}
app.replicate::<TrialVec>();

Self::add_random_to_trial_vec.run_if(has_authority()))

    fn print_trial_vec(mut trial_vec: Query<&TrialVec>) {
        let trial_vec = trial_vec.get_single_mut().unwrap();
        info!("Trial vec: {:?}", trial_vec.trial);
    }
    fn add_random_to_trial_vec(mut trial_vec: Query<&mut TrialVec>) {
        let mut trial_tvec = trial_vec.get_single_mut().unwrap();
        trial_vec.trial.push(1);
    }

commands.spawn(TrialVec { trial: Vec::from([1,2,3,4,5])});

#

on the server it prints the right thing, on the client it doesnt

echo lion
rose patio
echo lion
#

commands.spawn((TrialVec { trial: Vec::from([1,2,3,4,5])}, Replication));

#

Presumably your replicated soldiers also have this?

rose patio
# echo lion Presumably your replicated soldiers also have this?

no my soldiers have commands.spawn(RigidBody()) and lots of inserts, then i duplicate the soldier position and other stuff that i need
if i try to do commands.spawn((TrialVec { trial: Vec::from([1,2,3,4,5])}, Replication));
i get a panic in fn print_trial_vec()
that says that there are multiple entities. This is strange to me because there should only be one right?

#

the panic happens on the client

echo lion
#

Are you running the system that spawns the trial vec on the client? I'm guess you spawn on server and spawn on client, and now you are replicating the server one to client so you end up with 2 on client.

rose patio
#

maybe i have to spawn the TrialVec only on the server?

rose patio
echo lion
#

Replication will spawn it for you on the client.

rose patio
# echo lion Replication will spawn it for you on the client.

called Result::unwrap() on an Err value: NoEntities("bevy_ecs::query::state::QueryState<&mini_wars::game_config::TrialVec>")
i was getting this but i thik is because there is some delay from when it actually starts replicating, but by using match it works. Thanks you very much i was going crazy to solve this

spring raptor
spring raptor
sharp roost
#

You should be making the transport yourself right? So you create the client id there, and can just store it in a resource yourself, this also makes you not rely on it being exposed through transport layers, since not all of them expose it

spring raptor
#

That what I would expect "glue" crate would do. I suggested the author to create such a crate, but he is busy at the moment, this is why you need to do it manually.
Or you could create such crate yourself.

candid eagle
#

Is ClientId not serialisable?

spring raptor
candid eagle
#

i'm not sure what's goin on with that though

#

it might be fine since its just a wrapper around u64 though

spring raptor
spring raptor
spring raptor
#

You probably just copied the example into your project and it does not compile because of the missing feature.
So just copy this line into your Cargo.toml.

#

@echo lion maybe re-export this feature from replicon?

candid eagle
spring raptor
#

@echo lion maybe we need to re-export this feature and keep it enabled by default?

candid eagle
#

is it possible to give a particular client authority over an entity created on the server

#

or is authority handled on a world by world basis?

spring raptor
candid eagle
#

authority as in changes made to the entity/component on the client with authority will be replicated to the server and to other clients

spring raptor
#

It's similar to how you would do it in Unreal Engine if you familiar.

candid eagle
spring raptor
candid eagle
spring raptor
#

But there are a few upsides.
For example, we can use varints for array and entity data lengths.

echo lion
spring raptor
spring raptor
#

@echo lion Answered the comments in the PR.
Yes, it's not obvious without the usage, I should have at least written an explanation in the PR description. But I wanted an early feedback because I'm not sure about the idea in general.

Did you have a different approach in mind?

echo lion
spring raptor
echo lion
spring raptor
#

Okay!

echo lion
#

Btw I am now finally working on client mapping, since bevy_simplenet_events is out of my head.

spring raptor
#

Awesome!

echo lion
#

I'm not sure where to put the ClientMapped component id. Right now it is in replication rules cause that's easiest.

spring raptor
candid eagle
#

is it possible to have entities on the server that only replicate to specific clients?

spring raptor
#

I think it's expected, let's see how it's faster for multiple clients...

spring raptor
#

@echo lion looks like I can't bind the same UDP socket twice for reading :(
So I afraid that I can't benchmark multiple clients scenario until we have in-memory transport.

viscid jacinth
#

How do you guys look at the benchmarks? what do you look at; just the total time it took to replicate?

spring raptor
#

Yes, just measure replication time of multiple entities.

#

Unfortunately, we are waiting for renet release in order to use in-memory transport, so tests are not preciese due to socket wait time.

viscid jacinth
#

but do you take into account just the time that bevy takes to run a frame?
I have a in-memory transport for my benchmark, and my benchmark is actually faster when replicating 10 entities compared with not doing any replication

spring raptor
spring raptor
# spring raptor <@648083596850102275> Implemented data sharing, for a single client with a trivi...

Since this unlocks varints usage, I tried it as part of this PR. It makes the different less noticeable.
But I'm not done, I think I can squeeze a bit more tomorrow, going to sleep right now. But I think that the PR can be reviewed as is.
Will try varints for ticks, but I need to figure out the internal API for it due to borrowing issues. And possibly unsafe for slicing since we do it a lot, but I need to benchmark it.

spring raptor
#

Opened a separate PR with benchmarks rework

#

Yep, it's slightly faster for 20 clients even with trivial component with a single usize inside. So I think that the change is worth it.

spring raptor
#

@echo lion Actually, no, most of the time it's slower, results just jump for a few %.
Take a look at the code, maybe I missing something.

echo lion
#

I will review, maybe this weekend or next week

spring raptor
#

Okay, I left some comments with explanations and added description to simplify the review process. To sums up if I'm not missing anything:

  • The upside of this change is smaller messages due to varint encoding.
  • The downside is that it's actually a bit slower to serialize / deserialize for small components. But most of the time the components are small...

Will need your opinion. Usually we should honor bandwidth more? But maybe we could not reuse buffers and somehow keep varints for numbers.

echo lion
#

Could you add a benchmark with more components and more complex serialization?

spring raptor
#

I could, but I think most of the time people send small components.

echo lion
spring raptor
#

Okay, will do

spring raptor
#

Tried with a string, it's significantly slower... Not something I would expect.
I published benchmark results in the PR comment.

echo lion
#

Hmm odd

spring raptor
#

Will wait for your review, maybe you stop some mistake.
But if not, maybe the approach is just slower. In this case I will keep some message-buffer architecture changes from the PR (you will see on review, it's much nicer), just refactor to sequential write as before, should be the same performance-wise.

#

Rebased to the latest master to make it easier for you to run benchmarks.
Thanks for the help in advance :)

#

Commits contain separate changes, so you can try it without varints too.

candid eagle
#

is there a recommended way to keep track of the players connected to the server on both the clients and the server (including detecting disconnects)
my current plan is a plugin that maintains a hashmap of entity ids by client id in a resource, is an rpc or replicated component based approach better?

echo lion
spring raptor
#

Yes, it depends on the game.
If it does make sense to have an entity that represents a player - go for it. If not - you can use a resource.

candid eagle
#

and you have to use that map manually whenever replicating a component or sending a message containing the Entity type?

spring raptor
spring raptor
# spring raptor No, no, it's the same as Bevy's `MapEntities` trait.

Bevy's trait have the same purpose - update components after scene deserialization. You implement it manually if you need your components with entities inside to work correctly with scenes.
But it works a bit differently - it spawns a new entity if no such entity present. But in my case I need more configurable behavior, this is why we have a bit different trait.
I talked with devs, maybe in the future they make MapEntities trait more flexible and I will be able to use it.

echo lion
candid eagle
#

does replicon handle heartbeats on its own or is that something you have to implement, cause i noticed that if i force quit a client i don't seem to get a disconnect event

echo lion
candid eagle
#

sometimes alt+f4

echo lion
#

The server will eventually time out the client. How long is your timeout interval?

candid eagle
#

i have this snippet which is just copied from the simple box example (except with some additional tracing calls)

fn server_connection_handler(
        mut commands: Commands,
        mut server_event: EventReader<ServerEvent>,
    ) {
        for event in server_event.read() {
            match event {
                ServerEvent::ClientConnected { client_id } => {
                    info!("Player Connected with ClientId {client_id}");
                    //Spawn the player's Player Entity and store its id
                    let player_entity = commands
                        .spawn(PlayerBundle::new(
                            *client_id,
                            Color::from_u64(client_id.raw()),
                            None,
                        ))
                        .id();
                    //Spawn the player's Player Controller
                    commands.spawn(PlayerControllerBundle::new(*client_id, player_entity));
                }
                ServerEvent::ClientDisconnected { client_id, reason } => {
                    info!("Player with client ID {client_id} Disconnected - {reason:?}");
                }
            }
        }
candid eagle
#

I probably didn't wait long enough

echo lion
spring raptor
#

Not sure if I understood the question. Do you have a patched renet and want to use it instead?

spring raptor
echo lion
#

Reviewing the buffer PR today

echo lion
#

@spring raptor it looks like you are writing to the shared ReplicationBuffer for every client, for every InitMessage and UpdateMessage method.

#

So it's slower because you have the cost of the original implementation + overhead from the ranges

spring raptor
echo lion
#

Oh I need to look deeper then

spring raptor
#

There is also write that I use in some cases where we don't reuse, like mappings, array sizes, etc.

echo lion
#

Is there a test for multiple clients with different acks/connection times (and hence different init messages/update messages)?

#

Won't you end up with multiple array endings for each client (due to end_array())? Hmm ok I understand why you do this now lol

spring raptor
spring raptor