#lightyear

1 messages ยท Page 5 of 1

wintry dome
#

visually it looks like they could be being snapped back to a 500ms old position each time

summer finch
#

Hey, I've having issues with using MapEntities to map a message from the client's world into the server world. Is this currently possible?

pine cape
#

Hi, yes it is. What kind of issues

summer finch
#

I've implemented the MapEntities trait for a client to server message, registered the message in the protocol, added .add_map_entities() but according to my logs the entity is not getting mapped:
message:

pub struct OpenContainer {
    pub entity: Entity,
}

protocol:

        app.register_message::<OpenContainer>(ChannelDirection::ClientToServer)
            .add_map_entities();

logs:

CLIENT: Opening container Entity { index: 321, generation: 1 }
SERVER: Opening container: Entity { index: 321, generation: 1 }
SERVER: Actual player id:  Some(Entity { index: 306, generation: 1 })

I've tried using sending both the entity Id of the Confirmed as well as the Predicted entity, but getting the same results

pine cape
#

The mapped entity already exists on the server when the message is received?

#

i.e. the entity was already replicated from client to server?

summer finch
pine cape
#

Yes, the flow is:

  • replicate entity A from Sender to Receiver. Receiver spawns A*
  • Receiver maintains an internal mapping of A -> A*
  • then if Sender sends a Mapped message containing A to the receiver, the receiver will automatically map it to A*
#

So basically it's always the receiver that can map entities

#

I have to think about how to make this available ergonomically, but on the receiver (in your case the client), you can map the entity yourself with ConnectionManager.replication_receiver.remote_entity_map.get_remote(local_entity)

#

(replication_receiver is private currently, but i can release a helper function to help you do that)

summer finch
#

I see, thank you!

summer finch
summer finch
pine cape
#

So:

  • sending from sender->receiver, we convert from A->A* in the receiver?
  • sending from receiver->sender, we convert from A*->A in the receiver?
    Yes that's a good point, it's just that it's hard to identify who is the receiver/sender, since the send_message API is just sending a message between 2 peers
#

I think my solution is not ideal as well actually. Because on the client you can map entities to the remote world, but on the remote world the server will try apply MapEntities again, and it's possible that those entities get mapped to something else

#

Maybe when sending messages that have a MapEntities implementation I should include a boolean that specifies if the message should be mapped by the receiver or not

summer finch
pine cape
#

Hm maybe, but i think there shouldn't be too much risks to just use the provided function currently

#

i don't think you'll have conflicts

summer finch
#

I see, thanks for your help. Time to do some testing ๐Ÿ™‚

#

I've been looking at how bevy_replicon handles it out of curiosity and it seems to always do the mapping clientside (on deserialization for server-to-client events and before serialization for client-to-server ). However it keeps client and server event separate so it doesn't seem directly applicable here

#

At least with my limited knowledge of the codebase lol

pine cape
#

I think replicon does the same thing, they do the mapping on the receiver side

#

oh you're saying they also map entities from client to server when sending?

cloud topaz
pine cape
#

Is it expected that you're adding the Client and Server plugins in the same NetcodePlugin?

cloud topaz
#

oh that was it! ๐Ÿ˜… thanks!

cloud topaz
pine cape
#

If it's to potentially running in host-server mode it makes sense; the only thing you want to avoid is adding the ClientPlugins if you're running a dedicated server

cloud topaz
#

i see noted i'll come back and handle that after i get the bare minimum working then

summer finch
pine cape
summer finch
jade ember
#

Is Lightyear able to synchronize relations? when i use aery for example?

stiff quiver
#

idt anyone gave it a try yet, might be worth the attempt ;)

pine cape
#

I'm not too familiar with that crate, but yea if it's just normal components it should work

stiff quiver
#

you would need to share the relation types etc between client and server and prob have to add a lot of internal aery components to the protocol which means you will likely need to modify it a bit to expose them (pub) but yea i feel like it should be doable

jade ember
#

Hm, alright. I'll give it a shot and Experiment a bit

#

Thanks guys

storm gulch
#

what is the best way to predict/interpolate a component contain Quat? I have a component HeadRotation(Quat) and I added prediction and linear interpolation for but it seem a bit laggy. I also use a custom system to sync the rotation to the Transform. I dont need correctness, I just need it feel smooth.

pine cape
#

or I think you could also introduce your own

storm gulch
#

Thanks you so fast, I wil try it tomorrow

storm gulch
#

I update my interpolate function to use slerp with the visual interpolation plugin and look pretty good for now.

#

Question about which schedule and system set I should put my sync_rotation_to_transform system in? I'm currently put it in PostUpdate and before the propagate_transforms system form Bevy.

#

Also the replicate update message is not reliable right? I have a Position component, right before the game start I update it and after that never touch it again. Sometime it fail to replicate and it stuck in the old Position forever. Is there any better way to fix this beside send the Position in a custom message when the game start?

#

also the Lifetime enum is seem private, I can't construct it for the ControlledBy component, have to use Default::default()

#

also can you insert the Disconnected marker component when Lifetime is Persistent. I can do that myself but lightyear already manage the ControlledBy I think it better lightyear also do that for me.

pine cape
#

But why don't you work directly with Transform instead of syncing rotation to transform? Transform has a rotation Quat

pine cape
pine cape
storm gulch
storm gulch
pine cape
#

What kind of game are you making?

storm gulch
# pine cape What kind of game are you making?

just turn based game with some basic movement with FPS camera. Still hitting the jitter when moving and rotating the camera at the same time. I just found a tutorial with Unity fixing that by moving the camera out side of player and manually sync the transform so I will try it out

storm gulch
#

Is the VisualInterpolationPlugin suppose to fix all the jitter or I have to to other things?

pine cape
#

You seem to be running in hostServer mode? Do you notice jitter for the host client? There is no replication at all since they are sharing the same world

#

I tried running your example and I don't get any jitter, do you have a video?

storm gulch
#

here is the video. I also lower the fixed time update to 32 hz

#

I though it should be smooth like this video
https://youtu.be/cTIAhwlvW9M?si=LJA2tmxa9eeDZa7o&t=126

The third part of my Rigidbody FPS Controller series. In this video, I covered slope handling, fixing the weird jitter, and improving the ground detection. In the next one, we will start working on the wallrunning *yay. Make sure to subscribe so you don't miss it :)

Discord Server Invite: https://discord.gg/6ASySnPVXB
GitHub Repository: https:/...

โ–ถ Play video
pine cape
#

hm idk it looks smooth to me

#

(not on your video, but if i run it myself)

storm gulch
#

May be you can try to lower the fixed update to see the jitter clearer.

    app.insert_resource(Time::<Fixed>::from_hz(32.0));
#

Also I'm on ubuntu.

pine cape
#

when you change the update_rate

#

you also have to change it in lightyear

#

hm i tried with a FixedUpdate of 10Hz and yeah it seems like it still jitters, let me check why

storm gulch
#

I just push the change with 32hz, also adjust the camera to look down, so it more like the youtube video

storm gulch
pine cape
#

I guess it could

#

but then lightyear needs to be created after DefaultPlugins

#

it looks like the issue is that the 2 values that we interpolate between are the same

#

I think in my tests I don't have any test cases where there are many frames running between 2 fixedupdates

storm gulch
#

Also I just try the release build, still the same

pine cape
#

i mean the logs show that it's interpolating though

#
2024-08-01T14:27:37.710164Z  INFO lightyear_jitter: LAST tick=Tick(10) transform=Transform { translation: Vec3(0.0, 0.0, 9.114611), rotation: Quat(0.0, 0.0, 0.0, 1.0), scale: Vec3(1.0, 1.0, 1.0) } visual=VisualInterpolateStatus { previous_value:
 Some(Transform { translation: Vec3(0.0, 0.0, 9.0), rotation: Quat(0.0, 0.0, 0.0, 1.0), scale: Vec3(1.0, 1.0, 1.0) }), current_value: Some(Transform { translation: Vec3(0.0, 0.0, 12.0), rotation: Quat(0.0, 0.0, 0.0, 1.0), scale: Vec3(1.0, 1.0, 1
.0) }) } overstep=0.038203545
2024-08-01T14:27:37.718436Z  INFO lightyear_jitter: LAST tick=Tick(10) transform=Transform { translation: Vec3(0.0, 0.0, 9.238949), rotation: Quat(0.0, 0.0, 0.0, 1.0), scale: Vec3(1.0, 1.0, 1.0) } visual=VisualInterpolateStatus { previous_value:
 Some(Transform { translation: Vec3(0.0, 0.0, 9.0), rotation: Quat(0.0, 0.0, 0.0, 1.0), scale: Vec3(1.0, 1.0, 1.0) }), current_value: Some(Transform { translation: Vec3(0.0, 0.0, 12.0), rotation: Quat(0.0, 0.0, 0.0, 1.0), scale: Vec3(1.0, 1.0, 1
.0) }) } overstep=0.07964958
2024-08-01T14:27:37.726546Z  INFO lightyear_jitter: LAST tick=Tick(10) transform=Transform { translation: Vec3(0.0, 0.0, 9.362661), rotation: Quat(0.0, 0.0, 0.0, 1.0), scale: Vec3(1.0, 1.0, 1.0) } visual=VisualInterpolateStatus { previous_value:
 Some(Transform { translation: Vec3(0.0, 0.0, 9.0), rotation: Quat(0.0, 0.0, 0.0, 1.0), scale: Vec3(1.0, 1.0, 1.0) }), current_value: Some(Transform { translation: Vec3(0.0, 0.0, 12.0), rotation: Quat(0.0, 0.0, 0.0, 1.0), scale: Vec3(1.0, 1.0, 1
.0) }) } overstep=0.12088708
#

so it's strange

#

ah but that's the player being interpolated

#

not the camera

#

Ah ok I see why now

#

When I apply visual interpolation, I do it without any change detection

#

to not trigger changes during the interpolation for no reason

#

but the Transform Propagate requires change detection to work properly

#

I can make it configurable I guess

#

Or you could apply visual interpolation directly to GlobalTransform

storm gulch
#

Does turn on change detection work? In my game, I manually sync the transform so it definately trigger the change dectection

stiff quiver
pine cape
#

no, that should be doable

#

i don't rly know the diff between transform and affineTransform

stiff quiver
#

like already computed the matrix basically instead of storing the components (translation, rotation and scale) separately

#

prob need to convert back to regular transform before interp

#

so should be better to interp on the transform and then properly propagate

pine cape
#

Use this

  VisualInterpolateStatus::<Transform> {
      // set to true so that Transform changes are applied to GlobalTransform
      trigger_change_detection: true,
      ..default()
  }
#

and

lightyear = { git = "https://github.com/cBournhonesque/lightyear", branch = "main", features = [
    "leafwing",
] }
storm gulch
#

it work great when I lower the update rate to 10 or 5, but when I set it back to 64, it still jitter

pine cape
#

I just tried it at 64Hz and it works for me

storm gulch
#

you can see the first video is more jitter than the second one.

pine cape
#

i'm not sure , on my laptop it looks fine

#

Try addnig


fn log_transform(tick_manager: Res<TickManager>,
                 time_manager: Res<TimeManager>,
                 mut query: Query<(&Transform, &GlobalTransform, &VisualInterpolateStatus<Transform>), With<Player>>,
                 mut c_query: Query<(&Transform, &GlobalTransform), With<Camera>>) {
    let tick = tick_manager.tick();
    let overstep = time_manager.overstep();
    for (transform, global, visual) in query.iter() {
        info!(?tick, transform = ?transform.translation, global = ?global.translation(), ?visual, ?overstep, "LAST - PLAYER");
    }
    for (transform, global) in c_query.iter() {
        info!(?tick, transform = ?transform.translation, global = ?global.translation(), ?overstep, "LAST - CAMERA");
    }

}

to debug the actual values

storm gulch
#

I will try again tomorrow.

dreamy silo
#

hey, i git cloned the lightyear lib and could run the spaceship example just fine which is great! but i also wanna try using this in my own project and building it out from there. I tried copying the entire folder spaceships folder the lightyear lib and running it in my own fresh project folder. I changed the cargo.toml to work from lightyear and lightyear common examples crates instead of locally using "path = ../lightyear" or "../common" etc etc. but i think im missing something when running cargo -- server. anyone have any idea what im overlooking here? seems like the code is running but i just get this in my console: IoNotInitialized.

#

oh actually. seems like the server is running. it just printing this error...alot.

I tried cargo run -- client-and-server and it runs just fine. so not sure what that is ๐Ÿ™‚

pine cape
#

hm i'm not sure; but client-and-server doesn't really run any IO, it spawns the client/server in 2 separate threads which communicate via channels

#

so I would definitely try to make sure that it works with just --server

dreamy silo
#

you're right. I redid the project and now the error is not there anymore. one terminal tab with --server and others with --client works fine

pine cape
#

awesome

dreamy silo
# pine cape awesome

I was bit quick. when I try to spin up client number 2 it just times out everytime. I guess its expected behavior if i run multiple clients on the same network as the first client?

pine cape
#

cargo run -- client -c 2

dreamy silo
#

oh derp

pine cape
#

i need to improve the error when the client ids collide

#

it's definitely not clear

dreamy silo
#

no worries. it actually said this in the readme lol

#

nice example code. that obviously worked. thanks. Sorry about that, im new to rust and now also bevy, so just tryna play around a bit. multiplayer games def has another level of complexity to it than single player ones. but using lightyear seems like a nice approach.

pine cape
storm gulch
#

the name of other players seem doesnt have the issue

pine cape
pine cape
storm gulch
#

I tried to debug a bit this afternoon, but still have no idea reading the logged value. How do I know its buggy or not?

pine cape
#

Check if it seems to be changing in a linear manner, and seem to be interpolating between correct values

stiff quiver
storm gulch
#

Also does the lightyear lwim plugin account this issue? #1034547742262951966 message

#

I think it may related

pine cape
#

no in the latest released version

#

but in the next version it will

storm gulch
#

Nice!!

storm gulch
#

It seems bevy 0.14.1 break lightyear

pine cape
#

ty, i'll publish a new version

#

done

wintry dome
pine cape
#

yeah, i'm excited for the next one, i have some ideas for another multiplayer game ๐Ÿ™‚

dull lion
dreamy silo
# pine cape no worries, ask if you have any questions or ideas for improvements. And yeah li...

Thanks man, will let you know if I have any further questions or suggestions for improvements. Not bad to build an entire game in just a week, impressive. Will see if I can transition from the spaceships example into a fun multiplayer 2d game but with 3d models and an orthographic projection. Inspired by the space MMO game called dark orbit reloaded. Fun online game I used to play when I was a child. It helps that this can be put into a wasm application.

#

I know itโ€™s an ambitious goal but Iโ€™ll just try my best and hopefully in the process Iโ€™ll learn something regardless if I finish anything or not.

pine cape
#

Good luck!

twilit valley
#

I'm wondering if anyone has used Lightyear for network intensive games? I'm working on a voxel game where each chunk can be up to 16kb, and its often loading thousands of chunks on startup. Most libraries I come across have a very hard time transmitting packets the size I need them. They struggled with this packet size, some did support splitting packets up but took a large performance hit so took far too long to transmit the chunks. Wondering if I have any options here or if lightyear wont work for my use case :) Currently using Quinn directly but would love the advanced functionality of this library

pine cape
#

Lightyear should be able to transmit packets of any size; but I think it does splits packets similar to what other libraries do (there's not 10 different ways to split a packet that is too big over the network). What kind of performance hit did you take when transmitting the chunks? And were you transmitting them reliably or unreliably?

I would say give it a go and let me know if it works

twilit valley
# pine cape Lightyear should be able to transmit packets of any size; but I think it does sp...

Thanks for the reply! ๐Ÿ˜ Thats good to know it has built in splitting. I did a test using Messages before sending my message, since i'd like to control the ordering and prioritisation of the chunk loading. I kept getting
2024-08-03T06:07:28.547866Z ERROR lightyear::transport::webtransport::client_native: send_datagram error: TooLarge errors so just assumed that wasn't implemented.

stiff quiver
#

interesting, maybe replication group issue @pine cape?

#

Oh actually looking at the code it seems hes just using messages

twilit valley
#

If it helps I did try make a simpler reproduction without the moving box and that worked fine

stiff quiver
#

not sure if messages actually have packet chunking at all

#

or if they are just sent raw through the channel

pine cape
#

Hm for some reason i'm not able to reproduce this easily

#

I try moving around or pressing M 50 times but i don't get any error logs

#

ah it's with the moving box? maybe the MTU that i set for fragmentation is somehow bigger than webtranport's datagram MTU?

#

idk how you did it lol

dreamy silo
#

what's the main difference between lightyear and renet?

pine cape
#

renet just handles handling raw bytes over the network (with reliability, channels, ordering guarantees, etc.)
Lightyear adds some higher-level logic on top of that to do:

  • sending inputs from client to server
  • replicate entity/components
  • have prediction/interpolation/rollback
dreamy silo
#

Makes sense. So perhaps lightyear will look a little smoother moving 3d models around?

pine cape
#

moving 3d models around should be perfectly smooth

dreamy silo
#

Actually true, thatโ€™s what I saw in the bike game so that sounds very good. Alright thanks will play around with that.

twilit valley
#

Just making sure we got the right branch cloned and everything, you were seeing Sent chunk at message in the client log upon pressing m on the client?

pine cape
#

yep

pine cape
#

ah i managed to get it after i changed the number of chunks from 12 to 50

#

ok I think I know why that happens

#

i wouldn't be too worried

twilit valley
#

Is it something that needs to be mitigated?

pine cape
#

no there's just a packet limit size of 1300 and it looks like somehow we can reach 1301

#

i just need to tweak a couple values

pine cape
#

it's so hard for me to reproduce the error though, it happens really rarely for me

twilit valley
#

Is there any way I can help with logs or anything? Strange it happening differently between machines

twilit valley
#

unable to reproduce on your branch as well ๐Ÿ˜…

#

what the heck

pine cape
#

i think you have to be fairly unlucky to run into this

twilit valley
#

the logs really reduce the chance of it happening for some reason. Just upped the number of chunks being sent to 8 * 8 * 8 which would be a normal relatively small world load, and it makes the box jump off the screen and after a couple of seconds breaks with the TooLarge error. Seems like maybe i'll hold off on Lightyear for now ๐Ÿ™

earnest rover
#

I'm about to integrate Lightyear in my game. It's a steam game, and I would like the lobby owner to be the server. As I read it HostServer is the way to go. That means that all instances run HostServer, even if they are just clients many times. Is it a valid approach, or will I feel The Pain later on?

earnest rover
pine cape
pine cape
# twilit valley the logs really reduce the chance of it happening for some reason. Just upped th...

The box jumping off the screen is independent from the TooLarge error.

  • The TooLarge error is because the max packet size is 1284 bytes, and I probably miscalculated the fragmented packet size so that 1285 bytes is reachable. I wouldn't worry about it.
  • The box jumping around is because your bandwidth is being saturated by your messages, so your replication updates related to the box are not being sent. The solution would be to have a bandwidth limit (see here) and assign a priority to your messages. By default the EntityUpdatesChannel has a prioriry of 1.0, so you could try setting a lower priority to your other messages to guarantee that the replication updates are still included in every message
pine cape
pine cape
pine cape
#

although it's truly mysterious how adding the logs prevent the error from happening in your demo, I really don't understand why

twilit valley
twilit valley
pine cape
#

Hm your video doesn't exist anymore I think; the pink box moving is replication-data (By replication data I mean components/entities getting replicated)

twilit valley
#

The TooLarge error is because the max packet size is 1284 bytes, and I probably miscalculated the fragmented packet size so that 1285 bytes is reachable. I wouldn't worry about it.
Part of it that worries me is the server, not only will the client forever, but this means some rogue client can soft crash the server.

pine cape
#

I think it was something else actually; the QUIC MTU size was 1200 bytes, but we send packets up to 1300 bytes. I commented on your issue

twilit valley
twilit valley
#

I'm getting some errors trying to build main after the mtu fix so am not able to test if I still get errors ๐Ÿ˜ฆ

pine cape
#

ah sorry I made a mistake while merging

#

should be fixed now

twilit valley
#

Did some throughput testing, sending ~1.7k 16kb packets @ 40mbps, it took 25s from send_message_to_target to reader.read(). Meanwhile using an online calculator, sending that amount of data should take 45s ๐Ÿ˜ฏ is there compression built in or?

#

I'm still getting some jumping around when the bandwidth quota is reached even with that limit though ๐Ÿ˜ฆ since it means I need some metric to ensure I dont totally saturate the connection if I dont want players glitching around.
I'm also start losing frames the instant I press the button that sends all the messages, I assume that is from serializing all the data?

twilit valley
pine cape
pine cape
#

Maybe input and ping packets should be totally excluded from the rate limiter

twilit valley
twilit valley
pine cape
#

your branch is re-sending the messages from the server to the client; and there's no bandwidth limiting from server->client messages in your config

twilit valley
#

Yeah that was the branch I rebased

pine cape
#

that change is not in main, it's just in the cb/debug-big-packets branch

twilit valley
#

Yeah I was looking at that a bit confused, is it sending the chunks to itself by default?

pine cape
#

by default you would just use send_message which sends the message to the server_only

#

you can use send_message_to_target to send the message to the server, which will rebroadcast the message to other clients

#

if you put Target::ALL it will be rebroadcast to all clients, including the original sender

twilit valley
wintry dome
#

i'm using VisualInterpolationPlugin and VisualInterpolateStatus for avian2d's Position & Rotation. Works fine when i render the colliders using gizmos, with their positions and rotations taken from avian's components. However, once I add meshes / sprites, those are rendered based on the entity's Transform and they look stuttery (no interp). Since Transform isn't part of my protocol, i can't use it with the VisualInterpolationPlugin.

pine cape
#

I think you could try using Transform in your protocol instead of Position/Rotation

#

since avian2d keeps Pos/Rotation in sync with Transform anyway

wintry dome
#

my code uses pos/rot components everywhere atm, so it's not a small change ๐Ÿค”

pine cape
#

Make sure to use

  VisualInterpolateStatus::<Transform> {
      // set to true so that Transform changes are applied to GlobalTransform
      trigger_change_detection: true,
      ..default()
  }
wintry dome
#

yeah, i tried that and got the "not part of protocol" error

pine cape
#

I don't think you'd need to change any of your queries

wintry dome
#

yeah, oh i guess not if avian is syncing it yeah - maybe just change protocol from pos/rot to Transform is sufficient

#

let me try

pine cape
#

Are the meshes/sprites child entities of the parent entity?

wintry dome
#

some are yes

pine cape
#

Otherwise maybe the problem is the ordering of avian's pos/rot -> transform sync and visual interpolation

#

The order should be visual_interpolation -> pos/rot -> transform sync -> transform propagate

#

Maybe add app.configure_sets(PostUpdate, VisualInterpolationSet.before(PhysicsSet::Sync))

#

(or whatever the name for the visual interpolation set is)

wintry dome
#

i added this:

app.configure_sets(
    PostUpdate,
    InterpolationSet::VisualInterpolation.before(PhysicsSet::Sync),
);

hoping that the interpolated values would get synced to transform, but i still see the stuttery discrepency between the locations of pos/rot and the meshes.

I'm drawing gizmo outlines in PostUpdate, based on pos/rot components, scheduled .after(InterpolationSet::VisualInterpolation), and they are moving smoothly.

#

(PhysicsSet::Sync is scheduled before TransformPropagate)

pine cape
#

hm that's strange, I think you'd need to print the values of Pos/Transform/GlobalTransform in Last to see what's going on

#

I would expect it to work

wintry dome
#

ok

pine cape
#

Maybe set trigger_change_detection: true,

#

since Pos/Rot doesn't change with visual_interpolation by default

#

maybe avian2d doesn't apply any sync

wintry dome
#

oh, yes

wintry dome
#

player pos dumped in Last schedule:

Position(Vec2(1015.0073, -307.67514))
Transform(Vec2(1016.48627, -305.58356))
GlobalTransform(Vec2(1016.48627, -305.58356))
pine cape
#

so sounds like pos is not synced?

#

or visual interp. is done after sync somehow

wintry dome
#

i presume pos is the interpolated value, since that's not reset until preupdate or something

#

ie, before interp, pos would have matched trans

#

but i want them synced. i'll look into the schedule ordering some more

#

hmm when do the transform values get copied and sent to render world, i assume last, will check that too

wintry dome
pine cape
#

ah wow

#

i checked the SyncPlugin code and saw that by default it used PostUpdate

#

didn't realize that the PhysicsPlugin schedule was propagated to all the subplugins of avian

wintry dome
#

yeah ๐Ÿซ 

#

everything buttery smooth now โœ…

pine cape
#

nice!

#

I'm not sure if this is an ideal solution, as we might want to run the SyncPlugin in FixedUpdate too in case the user modifies Transform

wintry dome
#

hmm, yes I only sync pos to transform, not the other way around.

pine cape
#

but yeah this works if you won't update Transform directly

dull lion
#

When I used avian I disabled syncing transform to position to avoid confusion and to have one source of truth

#

Though when youโ€™re making a general library I guess you donโ€™t have the luxury of choosing

wintry dome
#

@pine cape are there any pros to the #2 interp approach? there is one listed, but it doesn't seem like a pro

pine cape
#

Yeah the main pro is that the simulation is completely up to date

#

With 1 one, the visuals are always one tick behind

#

With method 1, If overstep = 0.33, we interpolate 33% between the previous tick and the latest computed tick.
With method 2, if overstep = 0.33, we interpolate 33% between the latest computed tick and one extra tick. If we have inputs for that extra tick it will be accurate. It just seems more complicated to code; I don't even know if takes more CPU because potentially the extra tick could be re-used for next frame's simulation

#

Method 1 works well where FixedUpdate is very quick (64Hz), because 1 visual tick delay is basically nothing.
But if the FixedUpdate is slow (10Hz), the visual delay is actually pretty big, and it might not cost much to do one extra FixedUpdate

wintry dome
#

ah yeah i see what you mean, fair enough. but yeah, harder to code. i'm adding back the 2 alt interp methods, just reformatted a bit

#

method 2 does assume inputs for something the player hasn't seen yet, so sometimes it'd be inaccurate

#

only accurate if inputs for current frame and inputs for the next frame end up being the same

#

although input delay has your back i suppose

pine cape
#

we could just actually run an extra tick and use the input for the next tick in the input buffer

#

but yeah the code has to be adapted so that the inputs arrive slightly earlier

#

(so that the next tick is ready)

wintry dome
#

yeah

#

pushed an update to readd the alt interp methods, and a caveat about syncplugin

pine cape
#

wow thanks, that's a really good write-up!

wintry dome
#

glad i finally tracked it down, hopefully the book will save someone else several hours heh

novel hound
#

i don't know what the avian2d feature does but why is there no avian3d feature? is it not supported or is it usable but without integration?

pine cape
#

it is supported; I just didn't add it because I had no need for it but it can be added

earnest rover
#

I'm struggling with using both the default SteamWorksPlugin from bevy_steamworks and lightyear. I try setting the steamworks client on server::NetConfig::Steam to the same as the one added by SteamworksPlugin, and this issue points to a solution, https://github.com/cBournhonesque/lightyear/issues/552. However, the SteamworksClient only has private fields and can only be instantiated by creating a new client. Or, at least this is where I'm stuck, but there is probably something I'm missing?

GitHub

A networking library to make multiplayer games for the Bevy game engine - Issues ยท cBournhonesque/lightyear

dull lion
#

it's a pretty minimal plugin anyway and you can probably do without it

earnest rover
#

thx. I'll try that route.

jade ember
#

just keep in mind that lightyear only runs the callbacks once you're connected. if you need to do anything steam related when not connected, you need to run them yourself

novel hound
pine cape
#

you probably shouldn't enable it

wintry dome
#

i've been considering adding sound effects, and read this about sound in rollback: https://johanhelsing.studio/posts/cargo-space-devlog-4/ he's using ggrs though, and it looks easy to arbitrarily rollback clientside entities just by adding a Rollback component. is there a way to do something like that with lightyear? the sound entities wouldn't be part of the protocol

pine cape
#

from what i've read, in general sounds aren't rolled back because you could launch the same sound many times.
I would expect the architecture to be:

  • there is a networked entity E
  • when it does a given action, it triggers a sound (Via event or observer)
  • when E rollbacks, it redoes previous actions, which retriggers the sound
#

It sounds like you have a different model with 'sound entities'?

#

But yes there seems to be a need for non-replicated client entities to be able to rollback

wintry dome
#

need a way to cancel sound effects that were mispredicted. he's using rollback to create a desired sound state, and comparing with actual later and consolidating

#

so if you're playing shoot.wav because of a mispredicted shoot, then rollback removes it, the desired state (which got rolled back) wouldn't contain "play shoot", and it can be noticed and the effect stopped. seems like an elegant way to do it

pine cape
#

I think actually usually you don't want to cancel the sound because it can be jarring; I believe that's what the guy in the RocketLeague video was saying

wintry dome
#

so we'd need a way to register components for always-rollback without them being in the network protocol, and then maybe add a marker component like Rollback to them ๐Ÿค”

#

sometimes you need to at least fade it out, like if you mispredict the player death

#

not stopping that sound would be worse i think

pine cape
#

Yes, it's case by case basis

#

So currently we have:

  • check_rollback: a Predicted entity with PredictedHistory<C> for protocol components marked as predicted. It checks for server updates on the corresponding Confirmed entity; if there is a mismatch we enter rollback
  • prepare_rollback: for each entity, we reset the state to the Confirmed state (which we know is the correct state even if we haven't received an update for this specific entity, because all predicted entities are in the same ReplicationGroup. So if A sends an update for tick 17 but B doesn't; we know that B did not change (and not that the update is delayed)). Also handles Correction
  • execute_rollback: run FixedUpdate n ticks

It sounds like we have some cases where we have Predicted entities (live on the client timeline) but are not networked or relevant to the server, so they don't have a corresponding Confirmed entity. For example a "sound entity" as you mentioned. We could also have a Predicted entity that has a corresponding Confirmed entity, but which also has components that we want to rollback even though they are not networked.
So one solution could be:

  • we can register some components as being 'rollbackable'. Every component that has prediction enabled with ComponentSyncMode::Full is rollbackable
  • for those entities, we add Predicted but Predicted.confirmed = None (there is no Confirmed entity)
  • during prepare_rollback,
    • if there is a Confirmed entity, we reset the state of the ComponentSyncMode::Full to the Confirmed value
    • if there is no Confirmed entity, we reset the "Rollbackable" component to the previous state recorded in PredictionHistory
jade ember
#

Hello, when using mode HostServer, does the local client not emit a Connected event like the other clients do? or am i missing something maybe?

pine cape
#

I have to add a test, but it looks like it doesn't, you're right

jade ember
pine cape
jade ember
#

sure thing

jade ember
#

(first time really writing tests in rust, so bare with me)

pine cape
#

I would say to first just test it on one of the examples, by adding a system that reads the ConnectionEvent

#

otherwise I have test helper, it would look like something like this:

#[cfg(test)]
mod tests {
    use crate::tests::stepper::{BevyStepper, Step};

    struct ConnectCheck(count usize);

    fn receive_connect_event(reader: EventReader<ConnectionEvent>, mut res: ResMut<ConnectCheck>) {
        for event in reader.read() {
           res.0 += 1;
        }
    }

    #[test]
    fn test_host_server_connect_event() {
        // this creates a client and server app, but you would have to adapt it to start a host-server (might not be easy)
        let mut stepper = BevyStepper::default();
        stepper.client_app.add_systems(receive_connect_event);
        assert_eq!(stepper.client_app.world().resource::<ConnectCheck>().0 = 1);
    }
}

The tests are added in the same file, in a module called tests

pine cape
#

Actually i already have a HostServerStepper

jade ember
#

that would have saved me some time xD

#

thanks, will use that one

#
#[cfg(test)]
mod tests {

    use bevy::prelude::*;

    use crate::{prelude::server::*, tests::host_server_stepper::HostServerStepper};

    #[derive(Resource, Default)]
    struct ConnectCheck(usize);

    fn receive_connect_event(mut reader: EventReader<ConnectEvent>, mut res: ResMut<ConnectCheck>) {
        for event in reader.read() {
            res.0 += 1;
        }
    }

    #[test]
    fn test_host_server_connect_event() {
        let mut stepper = HostServerStepper::default();

        stepper
            .server_app
            .init_resource::<ConnectCheck>()
            .add_systems(Update, receive_connect_event);
        stepper.init();
        assert_eq!(stepper.server_app.world().resource::<ConnectCheck>().0, 1);
    }
}

i think before the test can even run, i'm getting following error:

---- client::networking::tests::test_host_server_connect_event stdout ----
thread 'client::networking::tests::test_host_server_connect_event' panicked at lightyear\src\protocol\registry.rs:59:13:
Type "lightyear::inputs::native::input_buffer::InputMessage<lightyear::tests::protocol::MyInput>" already registered
#

i'm not sure what causes that

pine cape
#

it probably because the InputMessage tries to be registered twice int he protocol

#

i'm not sure why, because hte ProtocolPlugin is added only once

jade ember
#

yes that confuses me too

wintry dome
pine cape
#

yes

pine cape
#

@wintry dome did you have a summary somewhere of how rollbacks should work with PreSpawnedPlayerObjects? I noticed that in bullet_prespawn example, the shoot_bullet doesn't have a run_if(not(is_in_rollback)), and I'm having a hard time reasoning about all this.
Do we trigger a rollback everytime we receive a pre-spawned-player-object and the authority goes to the server?

wintry dome
#

i'm not too familiar with the bullet_prespawn example, let me a have a look

#

hot take, as i have to head out in a bit: in that example i think shoot_bullet should be a no-op if running on a client and the queried player's InputBuffer is missing inputs for the current tick. ie, don't predictively spawn a bullet for a remote player based on an assumed input. also it shouldn't run in rollback on clients, since that case is covered by the server spawning the bullet and the components being replicated normally.

novel hound
#

I expected having to do workarounds and stuff

#

this immediately quadrupled my respect for lightyear

pine cape
#

aha you should test it first because I couldn't test it myself

novel hound
#

I will I will

#

one thing I will say about lightyear

#

too much boilerplate for my taste but that is fine

pine cape
#

To set up a new project you mean?

novel hound
#

yep

pine cape
#

yeah it's because i try to support a lot of different configurations. I'm sure there's plenty of ways to make it more ergonomic though

novel hound
#

i think it's fine, there are alternatives for simpler configurations

#

I used to use replicon but it isn't enough for this project

#

i need the complex configurations so this is expected

#

lightyear fills that gap

#

it's only my first time using lightyear I might prefer it in the future potentially

earnest rover
#

I'm really having a hard time understanding the Steam client creation. Here is the config I have now:

    fn build(&self, app: &mut App) {
        let client = SteamworksClient::new(480);
        let steam_client = Arc::new(RwLock::new(client));
        let client_net_config = client::NetConfig::Steam { 
            steamworks_client: Some(steam_client.clone()),
             config:  SteamConfig{
                ..Default::default()
             },
             conditioner: None
        };
        
        let steam_transport = server::NetConfig::Steam {
            steamworks_client: Some(steam_client.clone()),
            config: server::SteamConfig {
                app_id: 480,
                socket_config: server::SocketConfig::Ip {
                    server_ip: Ipv4Addr::from_str("0.0.0.0").unwrap(),
                    game_port: 5001,
                    query_port: 5002,
                    
                },
                max_clients: 16,
                connection_request_handler: Arc::new(DefaultConnectionRequestHandler),
                ..default()
            },
            conditioner: None,    
        };
        let server_config = ServerConfig {
            shared: shared_config(Mode::HostServer),
            net: vec![steam_transport],
            replication: ReplicationConfig {
                send_interval: SERVER_REPLICATION_INTERVAL,
                ..default()
            },
            ..default()
        };
    }
}

There is no other place I create the client. But I still get "could not create steam server: SteamInitError(FailedGeneric("InitGameServer failed"))", at lightyear-0.16.4\src\connection\server.rs:162, and I do not understand why? I had to cut some code short as not to hit 2000 limit, but essentially just use the configs in the rest of the setup

#

That message, looking at the code, tells me it is trying to create it once more. This happens when I call commands.start_server()

dull lion
#

are you sure you want to use SocketConfig::Ip with Mode::HostServer? You probably want SocketConfig::P2P

#

i've no idea if that will affect your problem though

earnest rover
#

I'm not sure about anything ๐Ÿ™‚ But it at least started the server. I'll have to try further tomorrow and connect the client. thx!!!

dull lion
#

note that you will need two steam accounts to test it out

#

it's easier to just avoid steam connections during development

jade ember
#

And two seperate devices

pine cape
#

oh actually you provide your own steam client

inner hill
#

@pine cape do you think it'd be a good idea to just copy the common stuff for examples for a project?

#

It seems fairly useful

pine cape
#

Yea I think it can be useful, especially if you want to try several network configurations

jade ember
novel hound
#

is the "blueprints" pattern (as in replicon) supported by lightyear?

pine cape
#

What do you mean by blueprints

novel hound
#

ok I found it in the lightyear examples

novel hound
#

do I need to derive reflect and register my components if I want them to be replicated?

pine cape
#

no

novel hound
#

I'm doing something very wrong somewhere then

#

I can't get my stuff to be replicated

pine cape
#

did you add the components to your protocol?

novel hound
#

I did

#

e.g.

app.register_component::<BoxMarker>(ChannelDirection::ServerToClient)
    .add_prediction(ComponentSyncMode::Once)
    .add_interpolation(ComponentSyncMode::Once);
#

do I need a channel or is there a default one?

pine cape
#

there is a default one

#

and you added Replicate on your entity?

novel hound
#
commands.spawn((
    PbrBundle {
        mesh: meshes.add(Cuboid::default()),
        material: materials.add(Color::srgb(0.2, 0.7, 0.2)),
        transform: Transform::from_xyz(0.0, -2.0, 0.0).with_scale(Vec3::new(100.0, 1.0, 100.0)),
        ..Default::default()
    },
    PhysicsBundle {
        collider: Collider::cuboid(1.0, 1.0, 1.0),
        collider_density: ColliderDensity::default(),
        rigid_body: RigidBody::Static,
    },
    Replicate::default(),
    BoxMarker
));
``` is how I spawn them
#

are objects spawned before a client joins replicated?

pine cape
#

Maybe link a repository with the full code and I can take a look

novel hound
#

sure give me a minute

#

it is basically a disected version of the avian physics example

#

but 3d

#

simply clone

pine cape
#

and you see the entity getting spawned on the server?

novel hound
#

yep

#

I would assume it wouldn't be rendered on the client because meshes and stuff aren't replicated but this should tell me if it's replicated no?```rs
fn init_box(
mut commands: Commands,
boxes: Query<Entity, With<BoxMarker>>
) {
for entity in &boxes {
info!("BOX {}", entity);
}
}

pine cape
#

yea

#

hm maybe try running init on the server

#

only after the server is started

#

run_if(is_started)

novel hound
#

ok trying it

#

ok now the server does not run init

#

is_started must be never true?

#

or maybe it is true but at that point bevy doesn't run startup systems anymore (because startup is over or smth idk

#

or something else is wrong

pine cape
#

are you sure your server starts correctly? do you get any logs?

novel hound
#

I do not get any logs from lightyear on the server

#

but on the client it reports connecting successfully

#

therefore I assumed it was started

novel hound
pine cape
#

maybe not, i think i only print logs for webtransport

#

strange; let me try to run your example

novel hound
#

if nothing turns up I might try using the crates.io version

#

and not the latest master

pine cape
#

you forgot this: apps.add_plugins(|| ProtocolPlugin);

#

also make sure to run it after adding Client or Server plugins

novel hound
#

so I add that last?

#

ok now testing

#

wow this is a huge facepalm moment

pine cape
#

I will create an issue to error if no protocol is registered

novel hound
#

it still doesn't work with the run_if btw

#

no idea why

#

but it doesn't bother me it works without that

pine cape
#

because at Startup you're not connected; yeah that was bad advice

pine cape
#

@wintry dome I merged https://github.com/cBournhonesque/lightyear/pull/583 which adds rollback for components that are NOT networked from the server to client.

Sometimes you have components on the client that you want to rollback even though they are purely client-related (sounds, particles, etc.). You can now do this, by:

  • calling app.add_rollback::<C>()
  • adding the component on an entity that has the Predicted component. If the entity was not replicated from the server, you will have to insert Predicted yourself to let lightyear know that the entity is on the predicted timeline
GitHub

You can now use
app.add_rollback::<C>() on a non-networked component (for example a mesh, a sound, etc.)
to make sure that that component is affected by rollback.
It will only work if...

wintry dome
jade ember
#

Can I not send messages as a server? (at least in host server mode)

sending it as the local client works:

fn broadcast_transition_message(
    mut client: ResMut<client::ConnectionManager>,
    lobby: Res<LobbyResource>,
) {
    if lobby.players.iter().all(|(_, ready)| *ready) {
        client
            .send_message_to_target::<Channel1, _>(
                &GameRoundStateChangeMessage(GameRoundState::Waiting),
                NetworkTarget::All,
            )
            .unwrap_or_else(|e| {
                error!("Failed to send message: {:?}", e);
            });
    }
}

sending it as the server, no one seems to receive it:

fn broadcast_transition_message(
    mut server: ResMut<server::ConnectionManager>,
    lobby: Res<LobbyResource>,
) {
    if lobby.players.iter().all(|(_, ready)| *ready) {
        server
            .send_message_to_target::<Channel1, _>(
                &GameRoundStateChangeMessage(GameRoundState::Waiting),
                NetworkTarget::All,
            )
            .unwrap_or_else(|e| {
                error!("Failed to send message: {:?}", e);
            });
    }
}
#

actually seems like only the local client receives the message when he sends it, weird

pine cape
#

Hm that's weird, it should work. I tested it in a unit test and it seemed to work fine

jade ember
#

I'll have to Check again when i'm back on my PC, might have messed up somewhere

jade ember
#

My issue was that i was trying to read the server variant of MessageEvent instead of the client variant. fixing that it works as expected

pine cape
#

There's the aliases ServerMessageEvent and ClientMessageEvent that might help avoiding this

jade ember
#

ooh i see! didnt know about them, very useful!

inner hill
#

Would be cool to have a debugging system in the future that tracks history for longer

#

I forget where I saw it, but someone made a rewind state thing in bevy which seems fantastic for debugging issues/seeing the state where stuff breaks easily

pine cape
inner hill
#

Not directly related to networking but we already track history for rollback so less duplication of efforts maybe

pine cape
#

For sure a lot of things can be improvement on debugging UX. Keeping structured logs, etc.

inner hill
stiff quiver
#

ai too prob

jade ember
#

if i have something like this:

app.add_systems(Update, (insert_resource.run_if(is_server),do_something).chain(); 

and the server creates a resource in insert_resource that is set to be replicated, i expected the clients to be able to use the resource. but i guess it hasn't been replicated fast enough, as the client crashes because the resource is missing.
is there a certain system/systemset that i should order my systems to run after to make sure clients have the replicated resource? or something else i should be doing?

pine cape
#

do_something needs to use the resource?

jade ember
#

yes :)

pine cape
#

Depending on latency, clients might get the resource only 500ms later, so it's not really related to system ordering

#

You should either use Option<Res> in your system, and already pre-create the resource on the client with a default value

jade ember
#

makes sense.
actually thinking about it again, i could probably also just add a run_if(resource_added as this particular system only needs to run once

#

thanks :D

jade ember
pine cape
#

correct

jade ember
#

hm, then i might miss something because its not happening

pine cape
jade ember
#

yea sorry, i did miss to also register the resource for lightyear ๐Ÿ˜…

#

if i remove it on the server, does it also get removed on clients?

#

hm, could it be that lightyear constantly triggers change detection for replicated resources? i never access the resource mutably and still receive change events

pine cape
#

It triggers change detection if you receive an update from the remote for that resource

#

Are you using Bidirectional or ServerToClient?

jade ember
#

ServerToClient

pine cape
#

I would check if something is modifying the resource on the server

jade ember
#

all the server does is initialize it once

#

just a commands.insert_resource

pine cape
#

I justed tested it in the lobby example

#

I only receive a 'replication message' once

#

if I add a system that prints on is_changed on the client, I also get only one log

jade ember
#

thanks for testing this, have to see where i messed up again. but i genuinely don't have any direct changes

pine cape
#

Maybe add a log around that code I linked when you receive a resource update message

#

To check if you're constantly receiving updates or if it's something else

pine cape
#

@summer finch by the way I added entity mapping on the send side as well (on top of the receive side): https://github.com/cBournhonesque/lightyear/pull/591
There's still the risk of conflicts that we talked about, but it's only if both client and server are replicating to each other. I have ideas on how to solve it, but I think it's more important to have simple entity mapping for now

GitHub

Currently we only do entity mapping when we deserialize a message, by looking at the remote_to_local map.
We didn't do the reverse, which meant that sending a message referring to a local e...

jade ember
#

i basically removed all my systems, and then one by one readded some, until one started triggering my problem.
and its the one where i insert the resource... and that one only runs once

if i only have that one inserting the resource, and have no other system running that accesses the resource, except of one that prints when it changes, i keep getting changes detected

jade ember
#

i'm looking at your tests right now in shared/replication/resources.rs and notice, for example in the test test_resource_replication_via_commands, that you never call server_app.register_resource and the tests still pass... how?

is my understanding wrong, that i need to first register the resource and then start replicating it by calling commands.replicate_resource?
when i don't do both, the client crashes.

jade ember
pine cape
pine cape
#

Hm the test works in host-server mode if you replicate only to the external client

#

it seems to fail if you replicate from the server to the local-client (which is not something that you should do)

jade ember
jade ember
jade ember
#

yes, that fixed it! thank you :)

summer finch
#

Asking for advice, what resource/component should I be looking at for getting networking stats? E.g. bytes sent and received, number of replicated components, rollbacks, etc (whatever is available essentially)

I'm trying to debug some jittery physics and the more information I have the better

#

Nevermind, sometimes I miss the obvious :/. Just saw this on your readme:

Lightyear uses the tracing and metrics libraries to emit spans and logs around most events ( sending/receiving messages, etc.). The metrics can be exported to Prometheus for analysis.

pine cape
#

Actually you shouldn't be using metrics/tracing for this.
There are a bunch of Diagnostics exposed by lightyear that give you information on rollback, RTT, etc. Check you the spaceships example to see how you can display those diagnostics

sonic citrus
#

I'm curious about the viability of connection phases in lightyear. If I want to have a phase before the actual game starts where I can exchange some data between the client and server, but without replication/inputs/rollback/etc., then only change phase and start that higher-level stuff after that phase finishes, is this possible now?

pine cape
#

What would be the goal for this? To reduce CPU usage by not running prediction/inputs systems?

dreamy silo
#

hello again. sorry im slow on networking for games. I took the example lightyear/spaceships and tried loading in 3d models in the glb format instead of using the gizmos 2d lines. ive simply added scenebundles with assetserver loaded assets. I get both the ships to spawn on the server and see them there in the window but the clients cant see eachother ships even if theyre right next to eachother. on the server.

Do i need to add anything to renderer.rs which is loaded via shared.rs? server is using commands.spawn to spawn in the entities, then client is simply inserting the 3d model onto a player entity (?) is it fn draw_predicted_entities and/or fn draw_confirmed_entities that needs to be written so that it communicates from client to server, then renders to all other clients? tried getting wiser from the lightyear cheatbook to really understand the concepts but think im overlooking something important here.

not sure if relevant but adding this info anyways. all clients have their own camera so i can later add camera following for each player entity. server has its own camera for an overall view so I can see whats going on in the world. Im assuming all spawned cameras are always looking at the same world?

I commented out stuff from the example I know I dont need (yet.)

pine cape
#

Rendering should be purely a client-concern, it's completely unrelated to networking. You replicate the ship entities to other clients, and then:

  • you add the SceneBundle on the Predicted ship for the local client
  • you add the SceneBundle on the Interpolated ship for the other clients
#

You can remove the draw_predicted_entities or draw_confirmed_entities systems, because as soon as you add the SceneBundle your ships will be rendered

dreamy silo
# pine cape Rendering should be purely a client-concern, it's completely unrelated to networ...

Thanks for a quick response. Iโ€™ll remove the draw functions since scenebundles are rendered by themselves.

So when I spawn entities with a player component, replicate component and a scenebundle on the server handle connection function, why isnโ€™t it automatically replicating to my clients? (Like in the first pic above) Itโ€™s only showing its own spaceship on the client, which is inserted to the player entity in the handle new player function. I assume automatic replication is already added since the player entity has this component when being spawned? Where does adding scenebundle to the predicted and interpolated ship come in? Client or server side? Or does it have to be added to the component registry under protocol.rs? Currently it only has a player, color, name and lifetime component registered.

Sorry for the many questions, Iโ€™m just โ€˜dumbingโ€™ down the example a little in my own project folder so I can understand the basics and work from there with more advanced game logic. thanks again for your time. ๐Ÿ™

pine cape
#

SceneBundle is purely rendering related, so you don't want to replicate it at all.
You only replicate components like ShipMarker that let you identify that the entity is a ship, and then on the client only you add the SceneBundle to display a ship.
Only the client has components like Predicted and Interpolated.
You do not want to add your scene-bundle components to the registry, because you don't want to replicate those components

pine cape
#

@summer finch I have an idea for the entity-mapping.

  • 1st version: only the receiver applies entity-mapping. The problem was if the receiver (client) needs to send a message to the sender (server) about an entity, the entity is not mapped, so the user needs to map it correctly.

  • 2nd version: the receiver applies entity-mapping using the remote_to_local map, and the sender applies entity-mapping using the local_to_remote map. In most cases it works out, because only one peer has an entity map. For example server spawns E1, sends to client who spawns E2 and has the E1<>E2 mapping. When the client sends a message to the server, they map from E2->E1 using the local_to_remote map.
    The issue is that if the client spawns an entity E1 and replicates it to the server who spawns E2; and the server spawns an entity E1 and replicates it to the client who spawns E3; then if the client sends a message about E3, it will first map it to E1 via local_to_remote. When the server receives it, it will map it to E2 via remote_to_local. Basically the entity_map is applied twice, once on the send_side and once on the receive_side, which is a conflict.
    The conflict only happens if the same entity E is spawned on the client and the server and replicated to each other. It should be rare but it could happen.

Here's my idea to prevent this:

  • 3rd version: when an entity is mapped, we flip a bit on the entity itself to mark that it has been mapped. When the receiver gets the entity, it can check that bit to see if the sender already mapped it. If it's the case, it doesn't apply the mapping on the receive side again. We should be able to flip a high bit in the 'generation' because no entity would reach generation 2^31
sonic citrus
# pine cape What would be the goal for this? To reduce CPU usage by not running prediction/...

no, it's more for security and correctness. if an arbitrary client connects to my server, then I don't know yet:

  • if the client is authorized to be connected to me
    • if we replicate entities before the client is even allowed on, that would be a big security risk
  • what the clients username is
  • what the clients preferences are (e.g. language)
    likewise, the server might have some config data to send to the client. think convars in a source game.
    a configuration phase before the rest of the game would allow exchanging this data
wintry dome
sonic citrus
#

unless I'm misunderstanding connect tokens, they seem to be a netcode-specific thing

wintry dome
#

it is a netcode thing yeah

sonic citrus
#

yeah I saw the example, but it's using netcode specifically

#

I'm not using netcode so I'm not sure how to go about this

#

I know that webtransport uses HTTP headers, and I could probably carry an auth token through that (along with extra info like client preferences)

#

but I'm not sure how that would work in WASM, and I don't think lightyear webtransport lets you read HTTP headers of a connection, and reject/accept a client based on that

wintry dome
#

i've only ever used lightyear with netcode, so not sure what your options are

sonic citrus
#

even if I were using netcode, connect tokens would work for auth but not any of the extra info I need like client preferences and server convars

#

I still think a pre-game configuration phase would be useful

wintry dome
#

the connect token would be associated with an account, which the server could look up. the account would have prefs associated with it, so connect tokens give you enough flexibility to store whatever you need in relation to accounts

#

you'd typically want a separate service for that, which the server can talk to

wintry dome
sonic citrus
#

yeah I think that would be useful

#

disable replication, inputs, rollback, etc. everything apart from RPCs basically

summer finch
#

Yeah, I agree with the 3rd version. I don't think we can avoid communicating 1 bit per entity to support all use cases (since otherwise given some entity id without additional info the mapper could not know whether it's server-to-client or client-to-server replicated).

If cutting the number of generations by a factor of 2 turns out to be a problem we can think up a better solution later

summer finch
#

the only alternative I can think of would be to offload the decision of where to map each entity to the user by having separate ClientMapEntities and ServerMapEntities traits, but that would also not cover some cases with bidirectional messages

#

Or using configuration in the protocol, i.e. something like:

app.register_message::<Message>(ChannelDirection::Bidirectional)
  .add_map_entities_if_sent_from_server(MappingLocation::OnRemote)
  .add_map_entities_if_sent_from_client(MappingLocation::OnLocal);

(ofc with better names)

#

But then that would not support edge cases where a message contains multiple entities that should be mapped, some of which should be mapped locally and some at the remote. Personally I can't think of an example where this matters (I only use server to client replication), but I guess it could be an issue

#

Sorry, I'm just thinking aloud here ๐Ÿ™‚ TL;DR I agree that your idea would support the most use cases

wicked tulip
#

Does lightyear support adding and removing components?

wintry dome
wicked tulip
pine cape
pine cape
royal saddle
#

I started a somewhat clean new project setting up essentials again with lightyear / avian3d / tnua and I'm seeing a bunch of "Rollback check: mismatch for component between predicted and confirmed Entity" and trying to figure out where I'm missing something.
I currently have a scene with a ground collider and a single other shape that gets spawned when the client connects that has a collider,rigidbody and a tnua controller.
There is no movement / input, it's just the spawn + the physics that drop the shape to the ground.

If I remove the tnua controller, I get 2 or 3 of the mismatch messages, which may be normal (unsure?), but with the controller there are a ton which seems like I'm doing something wrong as I don't see what prediction can go wrong on the client.

Anybody have some idea what I might be missing?

#

(the mismatch happens for Position/Rotation/LinearVelocity/AngularVelocity, so all of the avian components that are defined in the protocol basically)
(this is on the main branch of lightyear)

pine cape
#

@royal saddle this might be helpful: #1189344685546811564 message
Basically I think it's because tnua doesn't use Position/Rotation to affect the entity. It acts directly on GlobalTransform

royal saddle
#

aha thank you

royal saddle
#

and which lerp function do you use for globaltransform?

#

or do you add it without interpolation_fn and correction_fn

pine cape
#

I should provide one, but you can also add a custom interpolation fn that linear interoplates the Translation and Rotation components

earnest rover
#

So; I have been struggling for quite a few hours on input. For context: I have the game running, steam integration is working. I can get the inputs from the client, the server responds nicely and the client and server shows what it should show. However; no matter what I try, I can't the input from the server. This is HostServer mode.

I assume a lot of things and have tried a lot of things:
*Moving the input to sharedplugin
*Connecting as Local on the server
*Connecting as the steamclient

The input from the clientplugin:

            FixedPreUpdate,
            buffer_input.in_set(InputSystemSet::BufferInputs),
        );
```
and the movevement on the server, which works for everyhing except the server itself:
````  app.add_systems(FixedUpdate, (movement,handle_flows,handle_base_growth).chain());````
pine cape
#

Are you using native or leafwing inputs?

earnest rover
#

native inputs, I tried switching to leafwing, but did not matter, so I ditched it for now

#

very simple inputs in my game ๐Ÿ™‚

#

I noticed the code app.add_systems( FixedPreUpdate, send_input_directly_to_client_events::<A> .in_set(InputSystemSet::WriteInputEvent) .run_if(is_host_server), );and thought that should shortcircuit the network

#

I based my setup from simple_box example, if that gives a clue

pine cape
#

What part is not working: the inputs from the local/host client are not being registered by the host-server? or is it the inputs of another client?

earnest rover
#

the input on the hostserver. Client input is working fine. So in my case the lobbyowner/host cannot play

#

I do everything on the server for now, so the client sends input to the server, entities spawn, and get feeded back to the client just fine

#

and visuals on the server is fine

#

it's probably something super stupid I did though

pine cape
#

To clarify, the host-server IS a client as well, so it's a bit confusing when you're saying "Client input is working fine". You mean that the inputs coming from the local client that is also the server is not working?

The server can never send inputs, only clients send inputs

earnest rover
#

Yeah; it's an app that has both client and server plugins. The one that is lobbyowner in steam starts the server, and others connect. But yeah; the local client input part is not working (same process as server). Which is one of the pieces of information I might be missing. Should I start the client and connect to the server within the same app? As with config.net = NetConfig::Local?

pine cape
#

Did you set Mode = HostServer?
The simple_box example does what you mentioned (host-server mode and native inputs), and the local client's inputs are handled correctly. Yes, the local client in host-server mode should use config.net = NetConfig::Local

earnest rover
#

Ok, I'll try some more, that narrows it down a bit

pine cape
royal saddle
#

Ok so adding GlobalTransform on top of Position and Rotation, reduces the mismatches to about 8 ticks (removing position and rotation made it worse)

#

let's see if movement now is smooth or also keeps generating mismatches :p

#

that seems to work well, occassionally a rollback, but that's expected I guess

#

not the flood I had earlier :D

earnest rover
#

I ran the simple_box example. It actually never triggers the server side eventhandler AFAICT, but the box moves because its a client predicted entity. I changed the trace to info in the movement system for ExampleServerPlugin, and added info for the move system in the client to check

#

I understand I might be in the wrong here, just trying to figure it out

#

Uncommenting the movement app.add_systems(FixedUpdate, player_movement); in the ExampleClientPlugin makes the box stand still, and nothing happens

royal saddle
# royal saddle that seems to work well, occassionally a rollback, but that's expected I guess

when I add a second client, it's fine until collision happens, then there are rollbacks for the entire collision duration.
I feel like that doesn't sound right. As only 1 client is inputting events and it's 1 continuous press.
Am I right that I should mostly expect rollbacks on key input changes (e.g. stopping the press) and that the above should in principle be relatively smooth?
(Just checking so that I know my code is still doing something wonky somewhere :D)

pine cape
# earnest rover I ran the simple_box example. It actually never triggers the server side eventha...

I don't remember this part of the code too well, but it looks like you're right:

  • the local client buffers Inputs
  • we don't send inputs through the network, we instead send them directly to the local client's InputEvents
  • the client code handles those inputs, which moves the entity (the local client and server are in the same world and share entities)

Does this not work for you? I would expect that you have some client logic that handles input movement even when running in host-server mode. Or do you need to have server-handling logic?

pine cape
# royal saddle when I add a second client, it's fine until collision happens, then there are ro...

There is a bug in avian currently which causes rollbacks to trigger on every collision. cc @young prawn
There is a PR to fix it: https://github.com/Jondolf/avian/pull/480
but it's a breaking change so I think jondolf wants to wait for the next major version

GitHub

Objective

Fixes Determinism with contactsย #406

We noticed an issue where the Collisions were using the order of Entity directly to sort the pair of entities in a contact.
The issue is that for ne...

earnest rover
#

No, I think it it would probably work for me. It's just that I need to wrap my head around modeling and understanding it properly

#

I'm wondering if dedicated server/client is simpler to reason about

royal saddle
#

the main thing you have to ensure is that certain systems don't run double in hostmode, otherwise it's the same as separate no?

sonic citrus
#

I'm trying to run the lightyear simple_box example with wasm using trunk serve and getting:

2024-08-16T17:40:35.475021Z  INFO :rocket: Starting trunk 0.20.3
2024-08-16T17:40:35.475824Z  INFO :package: starting build
   Compiling bevy_egui v0.28.0
warning: unused import: `LOCAL_SOCKET`
  --> lightyear/src/client/io/config.rs:21:50
   |
21 | use crate::transport::{BoxedReceiver, Transport, LOCAL_SOCKET};
   |                                                  ^^^^^^^^^^^^
   |
   = note: `#[warn(unused_imports)]` on by default

warning: `lightyear` (lib) generated 1 warning (run `cargo fix --lib -p lightyear` to apply 1 suggestion)
error[E0308]: mismatched types
   --> /home/boris/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_egui-0.28.0/src/web_clipboard.rs:233:13
    |
233 |         let Some(clipboard) = nav.clipboard() else {
    |             ^^^^^^^^^^^^^^^   --------------- this expression has type `Clipboard`
    |             |
    |             expected `Clipboard`, found `Option<_>`
    |
    = note: expected struct `Clipboard`
                 found enum `std::option::Option<_>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `bevy_egui` (lib) due to 1 previous error
royal saddle
pine cape
#

@earnest rover Yes, the host-server stuff is a bit tricky because it relies on 'tricks' to make it work. e.g. there is 0 networking or sending messages so sometimes (for example for inputs) we 'intercept' events and 'fake send' them. Pure server/client is definitely simpler to reason about. Another option is to use client-and-server mode, where the host launches 2 separate processes, one for the client and one for the server, they communicate via channels so there is 0 latency.

earnest rover
royal saddle
#

oh I'm totally new here as well, so take my input with a grain of salt :D

pine cape
sonic citrus
#

what version of bevy_egui is yours using?

pine cape
#

bevy-inspector-egui = "0.25"

sonic citrus
#

yeah I have the same

#

that's weird

#

cargo tree | grep egui?

pine cape
#
โฏ cargo tree | grep egui
โ”œโ”€โ”€ bevy-inspector-egui v0.25.1
โ”‚   โ”œโ”€โ”€ bevy-inspector-egui-derive v0.25.0 (proc-macro)
โ”‚   โ”œโ”€โ”€ bevy_egui v0.28.0
โ”‚   โ”‚   โ”œโ”€โ”€ egui v0.28.1
โ”‚   โ”œโ”€โ”€ egui v0.28.1 (*)
โ”‚   โ”œโ”€โ”€ bevy-inspector-egui v0.25.1 (*)
sonic citrus
#

ah

#

I have 0.25.2

#

if you cargo update and try running it, see if it breaks?

pine cape
#

i don't want things to break though :p

sonic citrus
#

ok

pine cape
#

just fix it to 0.25.1

sonic citrus
#

so it sounds like bevy-inspector-egui pushed a semver breaking change

#

very cool

pine cape
#

๐Ÿฅฒ

sonic citrus
#

weird, even after downgrading it's still broken

#

can you send me your Cargo.lock from the simple_box example?

earnest rover
#

I'll probably go for it now anyways. I do not want to figure out how to deploy a host-server to steam just yet (not AAA stamped for another week or two :))

earnest rover
pine cape
#

well all the examples are working in HostServer mode, so for now it seems like it's working for most features.

The only adjustments for HostServers are:

  • the world is shared between client and server, so the local client sees everything that the server sees
  • native inputs aren't forwarded to the server but they directly sent to the client's InputEvents
  • the Predicted and Interpolated components are added on the local client's entities as needed

So yea you have to be a bit careful with your filters to make sure that everything works in host-server mode.
Otherwise having separate client/server Worlds is definitely simpler. (i.e. running 1 thread for the local client and 1 thread for the server)

summer finch
pine cape
#

@sonic citrus do you have a fix for that egui thing?

pine cape
#

I finalized the addition of authority transfer in lightyear!
"Authority" means which peer (client or server) is allowed to simulate the entity and send replication updates about it. With this feature you can now easily swap the peer that is simulating the entity

#

In this video the ball is white when it's simulated by the server, when it gets close to a player's entity, the authority is transferred to that player and they are the ones who are now simulating the ball. The server just receives the updates and broadcasts them to other players

#

This requested a lot more work on the entity-mapping, since we really want to be able to entity-mapping on the sender side. I had already updated messages so that entity mapping can be done on the send-side for messages, but now it can also be done with components.
(i.e. server spawns E1 and replicated to client. Client spawns E1 and maintains a mapping E1<>E2.
When the server sends a replication update about E1, the client maps E1 to E2 before applying the update.
Server transfers the authority to the client. The client must now do the mapping from E2->E1 because the server doesn't have any mapping available)

sonic citrus
wintry dome
pine cape
#

Released a new version: #crates message

earnest rover
#

Hi again. My inputs are working fine now, and everthing is nearly good. I spawn entitites on the server, and they are transferred to the client. For my game, I only transfer entities like Base, Occupied etc. On the server, I spawn them as a child of a hex in a grid, and everything works fine. On the client, the entity gets spawned, but of course not attached to the hex entity as a child, since all clients have their own copy of the hexgrid.

I plan to handle the entityspawn event and attach it as a child to the correct hex on the client

I'm fine with this, just wondering if it's good or bad design to do it like this?

Attaching a picture from the server if it gives some clarity to the question
(HostServer mode, but I assume that does not matter here)

#

The idea is to only transfer the entities that affect the world, and let the client do all the visuals, hence the local copy of the hexgrid from a seed delivered from the server. In my head that sounds like a reasonable thing to do

pine cape
#

And you don't want to replicate the hexgrid to avoid wasted bandwidth? instead it's deterministically generated from a seed?

earnest rover
#

exactly

#

It's not the bandwidth itself, it's the principal of the thing ๐Ÿ™‚

pine cape
#

yea i think reacting entity_spawn (or maybe to component_insert of a marker component) would work for this

earnest rover
#

I have set the hierarchy: ReplicateHierarchy {
recursive: false
} on all Replicate bundles

pine cape
#

Otherwise what you could potentially do:

  • add the ParentSync component on your child entities in the server
  • manually add a mapping from the server_entity to the client_entity in either the client or the server. The initial message that transmits the seed could also transmit the server_entity so that the clients have access to it. I'm not sure it's possible to do that currently because of visibility issues. The mapping (on the client) is in ConnectionManager.replication_receiver.remote_entity_map

Then on replication the hierarchy should be updated automatically

GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

earnest rover
#

cool, I'll try that! Thanks!

wintry dome
#

@pine cape btw did you try lightyear with avian main, with the physics sets in (the new avian default of) FixedPostUpdate? are there any scheduling issues you can think of where lightyear puts something at the end FixedUpdate thinking it'll happen after physics, but now it'll be before physics

pine cape
#

I wasn't aware they changed the default to FixedPostUpdate. It will definitely cause issues, the systems that update the PredictionHistory (used for rollbacks) are running in FixedPostUpdate. There's also some input systems related to delay which might conflict

young prawn
#

the PR where it was changed is here, it also has a brief section on why FixedPostUpdate instead of FixedUpdate

#

I did use FixedUpdate in that PR originally, but after some discussion in #1124043933886976171 we came to the conclusion that FixedPostUpdate is probably better

#

it's also more in line with how other game engines run physics

pine cape
#

Yeah I don't think it's an issue; users can override the schedule to FixedUpdate if they want to, and if they don't I think there's only one ordering; I can actually add it myself in lightyear

pine cape
lone silo
#

does the leafwing feature work with leafwing 0.15

pine cape
#

Yes

earnest rover
pine cape
#

You mean that you're not using parent/child?

jade ember
#

I noticed that whenever i stop my server, an entity with component ControlledEntities stays. and when i then start server again, i get a second one, and so on, slowly collecting more and more of these entities and they never despawn.

#

running in host server mode

earnest rover
pine cape
earnest rover
#

@pine cape I think it would be much easier to work with HostServer and inputs if the code here

/// In host server mode, we don't buffer inputs (because there is no rollback) and we don't send
/// inputs through the network, we just send directly to the server's InputEvents
fn send_input_directly_to_client_events<A: UserAction>(
   tick_manager: Res<TickManager>,
   mut input_manager: ResMut<InputManager<A>>,
   mut client_input_events: EventWriter<InputEvent<A>>,
) {
   let tick = tick_manager.tick();
   let input = input_manager.input_buffer.pop(tick);
   client_input_events.send(InputEvent::new(input, ()));
}```

Copied the events to the server::events::InputEvent instead of client::events::InputEvent.

That way there need not be a special handling on the clientplugin for this mode, and everything will happen has normal in the serverplugin.

I would be happy to do the change, but it will take some time, as everything is so new to me.

But if it is not a good idea, then I'd rather not do it ๐Ÿ™‚
pine cape
#

Yes I think I agree. In the examples there is no special handling for inputs in HostServer mode because we handle inputs the same way in the client and the server for client-prediction purposes, but in the general case I think the server should be the one handling the inputs

inner hill
#

.disable::<ColliderHierarchyPlugin>() whats this for?

#

does avian's collider hierarchy not work with lightyear or somethin?

pine cape
#

The server in the example is running with MinimalPlugins, which would break if the ColliderHierarchyPlugin is included. It might have been fixed now in avian

idle pier
#

I was wondering if I'm doing something wrong?

I have a type for the player id (like the avian example):

// Components
#[derive(Component, Serialize, Deserialize, Clone, Debug, PartialEq, Reflect)]
pub struct PlayerId(pub ClientId);

but whenever I add it to an entity, I get the following error:

ERROR lightyear::shared::replication::receive: could not write the component to the entity: SerializationError(BincodeDecode(Io { inner: Error { kind: UnexpectedEof, message: "failed to fill whole buffer" }, additional: 4 }))```
#

it seems to happen for any type that wraps ClientId

pine cape
#

Do you have a repo where I can reproduce the error? I've never seen that before

idle pier
#

it's essentially the avian3d demo

#

no other changes rlly

pine cape
#

well the avian2d demo works for me ๐Ÿ˜‚ so not sure what could be happening

idle pier
#

oh 2d* yea

#

i mean i'm using 3d avian

#

but this is the playerid thing

pine cape
#

it would still be helpful to see an exact list of changes

idle pier
#

could it be because of this client setup?

#
let config = ClientConfig {
    net: NetConfig::Netcode {
        config: NetcodeConfig::default(),
        io: IoConfig::from_transport(ClientTransport::WebTransportClient {
            client_addr: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0),
            server_addr: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), PORT),
        }),
        auth: Authentication::Manual {
            server_addr: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), PORT),
            client_id: rand::random::<u8>().into(),
            private_key: *KEY,
            protocol_id: PROTOCOL_ID,
        },
    },
    ..Default::default()
};
idle pier
#

as I cannot share this codebase sadly

#

sever setup:

let certificate = Identity::self_signed(&["127.0.0.1"]).unwrap();
let net = vec![NetConfig::Netcode {
    config: NetcodeConfig::default()
        .with_protocol_id(PROTOCOL_ID)
        .with_key(*KEY),
    io: IoConfig::from_transport(ServerTransport::WebTransportServer {
        server_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), PORT),
        certificate,
    }),
}];
let config = ServerConfig {
    net,
    ..Default::default()
};
pine cape
#

The server setup should be using Ipv4Addr::UNSPECIFIED in its server_addr

idle pier
#

oh right

#

is lightyear doing any encryption on top of quic?

pine cape
#

no

idle pier
#

hmmm

#

should that matter then?

#

i'll try and get a minimal reproduction case working

#

and share it ๐Ÿ‘

pine cape
idle pier
#

ahh yea ok

idle pier
pine cape
#

fixed

idle pier
#

turns out

#

the order at which components where being registered was different on client/server

#

bcz of some old bevy_replicon artifact in our code base

#

causing the PlayerId component to be deserialized as smth else

pine cape
#

oh ok nice

jade ember
#

I'm trying to sync my player movement and have referred to the Ship example.
My project is in 3D however and am running into some problems, and am quite confused by the whole thing.

  1. what is that second thing you can see in front of the camera that seems to copy my movement?
  2. why is my movement working fine, but my rotation (looking around) not? as far as i can tell, i don't have anything different
  3. when running physics plugin in FixedUpdate my movement is quite choppy... how could this be improved?

while writing this i tested out how it looks when another client joins, and turns out i get constantly rollbacks and the other player isn't even moving (locally and on server), so i guess 2. is only half true

pine cape
#
  1. What do you mean by "thinkg in front of the camera"? the blue line?
  2. It might have to do with how your inputs are computed + maybe input ordering?
  3. did you add visual interpolation plugin? Be sure to follow this as well: https://github.com/cBournhonesque/lightyear/pull/599
GitHub

Clarifies how the visual interp is set up, and fixes the syncplugin issue as described in the lightyear book VisualInterpolation chapter
This removes the stutter from the text labels that follow th...

jade ember
jade ember
#

so the other capsule seems to be my head, and the server was wrongly inserting player physics components into the head when replicating. fixed that

jade ember
#

soo i don't quite understand how to do the rotation... currently i'm modifying the Rotation of the player and i can see that it wants to rotate, but then it jumps right back (like in the gif above).

jade ember
#

before i was running the rotation and movement in 2 seperate systems, i now put them in the same one. movement is still working but rotation still has same problem as before

stiff quiver
pine cape
#

That might mean that the rotation is only applied on the client, so the server is resetting the value.
I would ask to print the rotation value on client and server. It might be due to transform propagate, or due to avian's sync. Did you order the Sync plugin from avian correctly?

jade ember
jade ember
#

i'll try some print outs on server and client, good idea

stiff quiver
#

its been a while since ive done it, ur prob better off asking in #1019697973933899910 or smth like that but im pretty sure that its a similar issue

pine cape
#

Also sync should be before visual interpolation I think? Check with the spaceships example

jade ember
#

the sync plugin?

pine cape
#

Yea, the syncplugin from avian

jade ember
#

the spaceship example only does this, which i also do:

app.add_plugins(
      PhysicsPlugins::new(FixedUpdate)
          .build()
          .disable::<SyncPlugin>(),
  )
  .add_plugins(SyncPlugin::new(PostUpdate));

can't find any other mention of SyncPlugin

#

plus that:

app.insert_resource(avian2d::sync::SyncConfig {
    transform_to_position: false,
    position_to_transform: true,
});
pine cape
#

Also 'change_detection: true' on the VisualInterpolationStatus component

#

But I'd try to print components values + ticks to debug

jade ember
jade ember
#

Okay, got it working, somehow... honestly don't know which exact change made it work.

now just the jumping isnt working for any non-host client...
and also, i'm getting a loot of mismatches between predicted and confiirmed (Position, Rotation and Linearvelocity) even when not moving... are there any common problems that i could have made / some general things i could do to improve this? any tips or stuff i can look for

pine cape
#

By mismatches, you mean rollbacks?

#

There must be something wrong then

#

Are you using tnua?

#

It could be:

  • system ordering issue (inputs and physics)
  • the same physics are not running on the client and server, maybe because some components needed to run physics correctly are not replicated
#

Idk why you would have rollbacks when not moving at all

jade ember
jade ember
jade ember
jade ember
royal saddle
#

or should all the input handling etc still happen in fixedupdate

wintry dome
#

i've been away for the last week, i switched to avian main but set physics to run in FixedUpdate, not had a chance to try that change yet

royal saddle
#

ok probably better for me to test in fixedupdate for now as well

#

guess I'm just gonna have to wait a bit for some releases, because I just get infinite rollbacks :(

royal saddle
#

I feel like I'm missing something fundamental again, I reverted to latest published versions of lightyear/avian, have a single gltf scene that gets loaded with colliders on client. When a client connects, the server spawns a cube with a collider that represents the player that gets replicated with prediction. This cube just falls to the floor of the gltf scene where it collides and comes to rest. On the server it actually comes to rest (the physicsdebug plugin lines go dark), but on the client it keeps spitting rollback messages and I can see the debug lines flashing. There is no user input whatsoever just the gravity of avian.

wintry dome
# royal saddle I feel like I'm missing something fundamental again, I reverted to latest publis...

i've a feeling there was some non-determinism that crept in causing issues. i think disabling avians sleep plugin is worthwhile during testing too. there was this PR that got merged 2 days ago which should help, although i've not tried it myself yet: https://github.com/Jondolf/avian/pull/480

GitHub

Objective

Fixes Determinism with contactsย #406

We noticed an issue where the Collisions were using the order of Entity directly to sort the pair of entities in a contact.
The issue is that for ne...

royal saddle
#

yeah the problem is that if I go to the main avian branch to get those changes, I also get those changes to the system in which avian now runs

#

I just compiled with the enhanced-determinism feature of avian, which does make the client stabilize, but not after a period of rollbacks, so yeah guess it's something with determinism

royal saddle
wintry dome
#

you can use main in FixedUpdate:

app.insert_resource(Time::<Fixed>::from_hz(FIXED_TIMESTEP_HZ));

app.insert_resource(avian2d::sync::SyncConfig {
    transform_to_position: false,
    position_to_transform: true,
});

app.add_plugins(
    PhysicsPlugins::new(FixedUpdate)
        .with_length_unit(PHYSICS_SCALE)
        .build()
        .disable::<SleepingPlugin>()
        .disable::<SyncPlugin>(), // run in PostUpdate instead
);
app.add_plugins(SyncPlugin::new(PostUpdate));
royal saddle
#

what does the SyncPlugin do? I feel like I haven't seen that before

wintry dome
#

(PHYSICS_SCALE defaults to 1.0)

#

syncs Pos/Rot to bevy's Transform, which is useful when you want to use the Visual Interpolation plugin, amongst other things

#

probably irrelevant to your current issues tho

#

i just copied that from my setup

royal saddle
#

ok thanks I'll try that out

#

and that's on main branch of lightyear & avian right

wintry dome
#

main ish yeah, recent enough

royal saddle
#

:D

#

that actually seems to work! nice thanks

#

adding the physicsdebugplugin seems to actually mess it up, maybe that was the culprit all along, maybe I need to run that in another timestep as well

#

nvm, removing it again and it's in rollback hell again

#

restarting the client sometimes gives a stable result and sometimes a perma rollback loop

wintry dome
#

weird, i've noticed something like that i haven't quite been able to pin down recently. but i swithed back to some non-networking work so not tried for a couple of weeks now. i did test the spaceships example in the lightyear repo though, and that seems to have no spurious rollbacks

pine cape
royal saddle
pine cape
#

I'm not too familiar with tnua's internals. Your movement code should be after lightyear's input system-set, so in FixedUpdate. Or in FixedPreUpdate (but you need to add an ordering constraint)

jade ember
#

hm lightyear doesn't check if a leafwing action is disabled?
i disable on the client, and on server its still active

pine cape
#

Correct, disabling was reworked recently in leafwing so I didn't add it yet

pine cape
jade ember
pine cape
#

how come you really need this? isn't it just bandwidth wasted by the client when sending data that the server doesn't need?

#

ah it's because the server needs to be aware that the inputs are disabled?

jade ember
#

Hm, yes. Because currently the Server just keeps receiving Inputs and thus keeps processing them, even when the client opened the Settings menu and we dont want him to Look around while in a Menu..

So the Problem at the Moment is, that the local Input processing correctly stops, but the Server still moves the client around lol

pine cape
#

I see

jade ember
#

I guess "really needed" is a bit exaggerated

#

But i figured it would be convenient to just disable/enable the Inputs when the game is being "paused/unpaused" by the client

jade ember
jade ember
pine cape
jade ember
#

It totally could be that i missed something, which is what i'm looking into a bit

jade ember
#

I messaged you privately!

sonic citrus
#

when I'm trying to set up a ClientPlugins and making a ClientConfig, I have to provide my IO layer there i.e.

ClientTransport::WebTransportClient {
                    client_addr: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0),
                    server_addr: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), PORT),
                }

But I want to specify the server address later, only after my player presses the connect button. how do I actually specify the HTTPS URL to connect to in webtransport, when I want to connect? not at plugin creation time?

jade ember
sonic citrus
#

in that case, is there a way to make a ClientPlugins without making a ClientConfig?

pine cape
sonic citrus
#

since I literally don't have any idea what server or IO layer I'll be using at startup

pine cape
#

You can just use ClientConfig::default()

sonic citrus
#

that works, ty!

#

is there a way to specify an HTTPS address for webtransport instead of a socket addr?

pine cape
#

what do you mean? like a url?

sonic citrus
#

yeah

pine cape
#

no you have to provide a socket addr

#

(when calling https under the hood it calls a socket_addr with port 443)

sonic citrus
#

hmmmm

#

that's not ideal

inner hill
#

is there a way to just completely disable preprediction?

#

I'm assuming I'm going to have to modify lightyear's plugins or something

pine cape
#

You can just not include the PrePredicted component

#

pre-prediction is just 2 systems so i don't think it's a huge priority to disable it

inner hill
#

well the systems exist and can be abused no?

#

since they'll just accept the client's data at its word and spawn it

pine cape
#

I guess. I could make the PredictionPlugin a PluginGroup so that sub-plugins could be disabled

#

but the problem is I think bevy doesn't support nested PluginGroups

inner hill
#

I guess if I just have all my components only ChannelDirection::ServerToClient it should reject anything that doesn't fit that right?

#

that should be fine

pine cape
#

Yes

inner hill
jade ember
#

(top is host, bottom is client)
this is on the latest commit of my project.
i'm still experiencing rollbacks, even if not moving.
something else... the head entity seems to not be replicated?
in the bottom you can see the client sees the hosts entity but only the yellow part, while the host (top) sees head (red) and body (yellow) of the client.
i'm not sure why. i have replicate hierarchy on true.
i tried to manually insert replicate into the head entity but that just makes it crash lol

pine cape
#

The rollbacks might just be because you have collisions, and avian's order of handling collisions is not deterministic?

#

Try with the simplest case: no hierarchy and only one entity

jade ember
pine cape
#

only one physics entity replicated

jade ember
#

The player Body is the only physics Entity, the head has no physics

#

And if only the local client connects i dont have any rollback issues, because he's the Server itself (Host Server Mode)

earnest rover
#

Are there any known issues with Name and StateScope? I use this syntax for a couple of prespawned entities:
.spawn(( Name::new("SpawnedBase"),StateScoped(GameState::InGame), HexData { hex })),
and they seem to remain on the client after exiting the state.
The commands are ran in OnEnter, app.add_systems(OnEnter(GameState::InGame), generate_new_world.run_if(is_host_server));
Everything else is spawned in other systems gets the correct name, and seems to be cleaned up properly on the client, and honors the statescope.
Perhaps it's something special with OnEnter in this regard?

Oh, and they do get properly cleaned up on the server (HostServer mode)

I might have some bug on my end, it's not unusual...

#

if there is a test somewhere that is nearly something like this, I'd like to try and reproduce with that, but I can't seem to navigate that good...

pine cape
#

By PreSpawned, you mean pre-predicted on the client?

#

It might be that the entity gets despawned on the client but then gets respawned whenever the server keeps sending updates?

#

There are no tests for this; it would be good if you could provide a minimum example

earnest rover
#

Oh, sry, no. I never prespawn. My game is simple... I mean OnEnter(GameState::InGame) if is_host_server

pine cape
#

But in which direction is the replication?

earnest rover
#

ServerToClient, I use that for all components

#

I'll try to add a test

royal saddle
#

I get some rollbacks with 1 client on the server

#

I'm still working on a minimal reproduction

pine cape
royal saddle
#

yeah I'm running server and client separately

#

also completely aside, what is an average compilation time in dev mode for any of you ?

pine cape
#

Incremental compilation is maybe 1-2 sec

royal saddle
#

wth am I doing wrong then ๐Ÿ˜…

pine cape
#

how long is it for you?

royal saddle
#

a minute

#

just removing or adding an info!

#

I've enabled dynamic-linking for bevy, I've the opt-levels

#

I just tried adding lld but that didnt really do anything noticeable

#

but well it's offtopic for this channel

pine cape
#

yeah in the past i was in a state where incremental compilation wasn't working anymore, but i don't remember how i fixed it

jade ember
#

so no i have not

#

but i'm also thinking about making a minimal reproduction

pine cape
#

@jade ember even running in client-and-server mode? (client and server in same machine, but different thread)

jade ember
#

didnt think of that, does that just require to change the mode to it? the local client is still Local, and so on?

#

let me try

pine cape
#

the local client is not Local, it's treated like any Netcode client

jade ember
#

i see

pine cape
#

it just has 0 latency because it sends messages via mpsc channels

#

but yeah i don't really get why you guys get rollbacks; i don't have any in my examples

royal saddle
#

once I figure out my compilation times I'll send you something :D

jade ember
unkempt sedge
#

Great physics example and so on you sir deseve a medal

idle pier
#

would client -> server replication be possible

#

on a component basis?

#

e.g. replicate one single component from client -> server

#

and the rest from server -> client

#

also, what's up with the rollback spam on info?

#
Rollback check: mismatch for component between predicted and confirmed Entity { index: 7307, generation: 2 } on tick Tick(12462) for component "avian3d::position::Rotation". Current tick: Tick(12465) predicted_exist=true confirmed_exist=true
#

movement/rotation looks fine

#

no stuttering etc

pine cape
idle pier
pine cape
idle pier
#

or

#

ah i can open a pr to resolve those

#

it did lead me to discover an issue haha

pine cape
#

yea it's the ChanenlDirection

idle pier
#

would that allow me to like

#

give local client authority over e.g. rotation

#

and replicate it back to other clients

pine cape
#

Hm i think a given entity can only have 1 authority

#

currently you cannot have the client entity be authoritative for Rotation and the server entity be authoritative for other components

idle pier
#

yea that makes sense

#

i guess i'll add it to the input message thing

unkempt sedge
pine cape
#

Well they do different things; client should have rendering-related logic
Server should have most of the core logic
For stuff related to prediction/rollback, the logic should be shared in both

unkempt sedge
pine cape
#

Only on clients I think, you usually don't need assets on the server

unkempt sedge
#

Oh man I should really have started my game with multyplayer now I have a lot of plugins that I need to classify lovely

pine cape
unkempt sedge
#

Oh that is lovelu

#

Interesting how your structure usually is split in two parts

pine cape
#

Usually it's 3; i always divide into client/server/shared

unkempt sedge
#

Ah yes, I am guessing physics mostly go there

#

Because of this fancy word I just learned roolback

inner hill
#

Yeah I've been integrating lightyear into my project and I think I might go the split route as well

#

too confusing otherwise

inner hill
#

i will say making them separate crates seems a bit tougher if you want to allow for local hosting though, so maybe just separate modules? The separate crates really helps with decoupling though

wintry dome
#

my top level cargo file has:

[workspace]
members = ["shared", "server", "client", "sandbox", "autoturret"]

where server and client are the respective binaries (that depend on shared). 90% of the code is in shared.

i'm experimenting with separate crates for self-contained bits, but to avoid circular deps i need the server/client to depend on autoturret and shared, since autoturrent also has to depend on shared. which is a bit annoying.
crates are the unit of compilation though, so it can be faster if i just change autoturret.. no need to recompile shared. still not sure if worth it, hence why everthing else is in shared atm.

#

if you set it up that way, remember you can use workspace dependencies, ie your deps are defined in the top level cargo, and shared/server/client just do bevy.workspace = true in their own deps section

#

(like jam5 does:)

inner hill
sonic citrus
#

I'm getting a weird crash with lightyear:

thread 'Compute Task Pool (0)' panicked at /home/runner/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bytes-1.7.1/src/bytes.rs:287:9:
range end out of bounds: 4257881868307136539 <= 1003
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `lightyear::server::networking::receive_packets`!
2024-08-30T10:39:44.148367Z ERROR lightyear::server::networking: Error during receive: failed to fill whole buffer
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!

https://hatebin.com/bnqlwockyz
from what I understand, it's trying to read some unrelated bytes as a VarInt, and trying to read way too many bytes out of a buffer. What I don't get is:

  • why isn't the server bounding how much it reads here?
  • why would the client be sending this invalid value that's read as a VarInt? what could cause this?
wintry dome
wintry dome
#

i wonder if we can detect that with a checksum that's exchanged after connect, at least in dev mode

jade ember
#

cc: @royal saddle

royal saddle
sonic citrus
#

thing is I use a shared crate for sharing protocol stuff between client and server

#

so I don't see how it can be different

pine cape
sonic citrus
#

it probably is the protocol, but I'm not sure what could be causing it

#

it's only the order of register_component calls that matters, right?

#

if I register XYZ on client and XYZ on server, in the same order, by just calling register_component, that's all that matters?

#

does register_type etc. matter?

pine cape
#

only register_component or register_message matter, yes. But if you're sure that everything is correct then it might be a bug

sonic citrus
#

well I can never be entirely sure that it's correct, could very well be me missing something obvious ๐Ÿ˜‰

#

but I'll keep digging and see if I can fix it

#

my server logs don't have these lines, whereas my client does:

component: register send events on client for lightyear::shared::replication::components::ShouldBePredicted
component: register send events on client for lightyear::shared::replication::components::ShouldBeInterpolated
floral meteor
#

hiya! what's the best way to map Entity components to their corresponding Entitys on the other side?

I have relations that are defined on the server (e.g. my Player has a CharacterId(Entity) component), but the client gets the server's entity ID for that character, not the client's, on the player

#

I see that there's entity mapping in place for messages, but I'm not sure how it works/if it does for components

pine cape
#

hey, it does work for components, you just need to do something like this:

#[derive(Component, Serialize, Deserialize, Clone, Debug, PartialEq, Reflect)]
pub struct ComponentMapEntities(pub Entity);

impl MapEntities for ComponentMapEntities {
    fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
        self.0 = entity_mapper.map_entity(self.0);
    }
}
app.register_component::<ComponentMapEntities>(ChannelDirection::ServerToClient)
            .add_prediction(ComponentSyncMode::Simple)
            .add_map_entities();
#

@jade ember it looks like

  • for the netcode entity, there's rollbacks of LinearVelocity. Also i notice the linear velocity keeps changing slightly
  • for the local entity, there's rollbacks of everything
#

It looks like the values oscillate between pos=Position(Vec3(0.0, 0.89997935, 0.0)) and pos=Position(Vec3(0.0, 0.8999793, 0.0))

#

Even when there is only the local client, the values oscillate a bit

wintry dome
# pine cape that looks insane! what are you going to do to optimize it?

it's a bit sluggish because i let the asteroids break into so many small pieces, can improve perf by reducing how many microscopic asteroids exist. most of the time was spent in physics collision work when it slowed down in that video. when i get back to the networking side of things, i'll probably have the clients predictively spawn the asteroids when one breaks apart. each asteroid has it's own rng seed already. the collider shape is generated from the rng seed already, which saves some bandwidth. i'll definitely need to figure out how to slow down the replication update rate for asteroids if i want that many replicated entities flying around. i'm still not really sure how many entities i can realistically support before networking becomes unreasonable, but it's going to be fun finding out.

#

i'm currently learning some astrodynamics. plotting minimum delta-v flight paths around attractors. trying to get my route planning and autopilot stuff working well enough to chuck a bunch of bots in that fly about looking busy. i have a semi-well-tuned pid controller now for bots to follow flight plans and move point-to-point. (none of that taxes the networking though). i'll probably run most of my bots as headless clients that connect to the server like normal players do, because solving the trajectories is cpu intensive

#

takes ~35ms to find that green route, which was the minimal delta-v required from the ships current linvel to get to the destination

#

(aka "Lambert's Problem")

pine cape
wintry dome
#

i'll do my best to do some profiling, once i have a setup that lets me easily overload the networking

#

i expect i can go pretty far by just massively reducing update frequency for things that haven't had a collision recently. the simulation is Mostly Deterministic..

floral meteor
unkempt sedge
#

@pine capeDId you utilize cargo packages feature to run the server and client distinctively or you just made them talk with each other via cli?

pine cape
#

I use cargo workspaces

unkempt sedge
#

Yeah gpt told me to start using that

#

Man I like you cat man you are very helpfull

pine cape
#

@royal saddle @wintry dome so it looks like the problem was this.

  • You spawn an entity 1 on client, with replication group id 10
  • You also spawn a child, and the Replicate components get propagated to the child.
  • BEFORE: I was using the parent's Entity (1) as the ReplicationGroupId
  • AFTER: now I use the parent's ReplicationGroupId (10) if present

It's not exactly clear to me why not having the parent and the child in the same replication group could cause issues.

  • One potential issue was that the Child could be replicated first, so when we try to add the Parent component on it, we fail because the Parent wasn't replicated yet.
  • Another potential issue is that a single message could contain only updates for the Parent, but not the Child (or the reverse). That means that the confirmed state received by the client was something like: (Parent: tick 13, Child: tick 11). Then when we do rollback between ticks 13 (latest_server_tick) and 17 (current_client_tick), we would rollback 4 ticks for all entities even though the child is actually 6 ticks behind. It's not clear to me why this would cause rollbacks even when entities are at rest.
#

I published a new version 0.17.1 with the fix

wintry dome
#

nice catch, i expect i would have run into that before long too

sonic citrus
#

how do I kick a specific client?

pine cape
sonic citrus
#

yeah I see that, and that's stored in what, ServerConnections?

#

right now I have

#
    mut servers: ResMut<ServerConnections>,
// ...
    let _ = servers.disconnect(*client_id);
#

which works, but causes this

2024-08-30T20:53:28.729407Z ERROR send_packets: lightyear::server::networking: Error sending packets: netcode error: tried to send a packet to a client that doesn't exist
#

that gets spammed out the console

pine cape
#

it gets spammed indefinitely?

sonic citrus
#

yep

pine cape
#

i'll create an issue

sonic citrus
#

is there a workaround for this (i assume) bug?

#

like manually remove the client so that we're not trying to send packets to him anymore

pine cape
#

hm i guess just disabling the logs from this file

sonic citrus
#

ah man

#

alright

sonic citrus
#

I tested with 250ms fixed + 50ms jitter using tc qdisc and it crashed almost instantly

pine cape
#

What kind of crash error do you get?

#

I've used the LinkConditioner to add latency up to 2s

sonic citrus
#

something internal in lightyear reading a varint improperly, and reading too many bytes

#

adding the LinkConditioner doesn't cause it, but adding delay using OS-level tools like tc qdisc does

pine cape
sonic citrus
#

nah, this one

pine cape
#

Oh nvm i see

sonic citrus
#

yeah it never happens on a local server, but happens often on a remote one, or when there's latency

#

real latency i.e. from network conditions or tc qdisc, not conditioned latency

#

and I've checked the protocol, looking at lightyear with rust_log=debug I can see that all components and messages are registered in the same order

pine cape
#

Do you have a minimum reproducing example?

sonic citrus
#

which leads me to believe it's something in lightyear

#

I can try to get a minimum reproducible example but it might take some time

pine cape
#

Sure, that's fine

sonic citrus
#

have you tried the lightyear examples with an OS-level conditioner? it might have the same behaviour there

pine cape
#

For the latest jam I deployed a game on real servers, there didn't seem to be issues

sonic citrus
#

not sure then, let me try building an example and running it with like 1000ms delay

wintry dome
#

fwiw i test with the tc qdisc equiv on mac, dummynet/dnctl, and haven't noticed any crashes due to high latency

#

i've tested in the <300ms range anyway

sonic citrus
#

also, I really don't think the entire server should be torn down if lightyear fails to read some packet data, and over-reads because of a client-sent varint

even though this exact instance is probably a bug in either my code or lightyear, the fact that lightyear reads a varint and blindly tries to read that many bytes (using the panicking bytes::Bytes API) sounds like it would be really easy to exploit from a client

#

trying it out with anything more than ~500ms delay in tc qdisc doesn't even let me run the examples, because it takes too long for the client to connect to the server

#

I was having timeout issues in my own app, so I raised the netcode timeout to i32::MAX (why is that an i32 btw? that should be an option), and maybe that's why I can reproduce it more consistently

#

Got the crash on avian example

pine cape
#

Please open issues with any identified problems or ideas for improvement

sonic citrus
#

here's a video of what happens, this is with 1000ms + 50ms jitter on tc qdisc (2000ms RTT). It crashes before the client is even replicated

#

I had to increase the timeout in the NetcodeConfig to i32::MAX

#

although in practice, this happens at much lower RTTs than 2000ms

#

so this is probably some sort of issue that gets amplified with how much data you send, since I send quite a lot of replication data

pine cape
#

I managed to reproduce the bytes panic with 1000ms latency (not link conditioner)

pine cape
#

@sonic citrus I just fixed the bug. The problem came from the combination of 2 things:

  • I was sending input messages even during rollback. With 2s latency there's a lot of rollback ticks (>100) so we're suddenly sending ~100 small messages in one packet. Now we're not buffering any input messages to send during rollback
  • There was a bug in the serialization code where the serialization crashed if more than 64 messages were sent in one packet.
    Both have been fixed! Thanks for reporting this
sonic citrus
pine cape
#

This makes me think that the user has to be careful about using functions like send_message during FixedUpdate. It's possible that the message can be resent during rollback as we replay FixedUpdate. Maybe I should provide a specific FixedUpdateWithoutRollback schedule

dull lion
pine cape
#

True. It's easy to forget though

idle pier
#

@pine cape would a feature flagged iyes_perf_ui integration be something you'd be interested in me upstreaming?

pine cape
#

That's cool! Maybe this could be added as an example?

idle pier
#

although i do think, because lightyear already provides the diagnostics stuff

#

it would make sense to maybe feature flag it?

pine cape
#

i'm not sure iyes_perf works, it takes diagnostics as inputs?

idle pier
#

you essentially provide your own "entries" to the ui

#

and the source for values can be anything

pine cape
#

I don't know if those kind integrations make more sense as a feature flag or as a lightweight separate crate. Probably the latter?

pine cape
royal saddle
#

nice, I'll check that out as well to see if I can improve my own code.
The rollbacks might be due to that determinism bug in avian that is fixed on main but not yet released

stiff quiver
#

gz to the author

dreamy silo
#

I cant app.register_component:: <Position> or <Rotation> from Avian3d. works fine with these same components from avian2d. this is the error I get. any idea how I can take care of the error?

#

without overwriting avian's pos and rot components by defining my own position and rotation components further up

jade ember
#

put this in the dependencies of your Cargo.toml:

avian3d = { version = "0.1.1", features = ["serialize"] }
dreamy silo
#

derp

floral meteor
#

hiya, not sure if this is a bug yet, but if I spawn an entity on the client and replicate it to the server, which then replicates that entity to the other clients with controlled_by: { NetworkTarget::Single(sending_client) }, should I expect to see the Controlled component for that entity on the sending client?

#

I suspect this won't happen for entities that start off being client-replicated because they're providing sync, so the server's addition of the Controlled component never gets added to them

pine cape
floral meteor
#

what would be the easiest way to determine if the local client's replicating a given entity, then?

pine cape
#

The easiest workaround is probably for you to add it yourself on the sending client

#

since you're initiating the replication from the sending client, you should have access to this information, no?

floral meteor
#

that should be fine, yeah

#

will it be removed once ownership transfers away from it?

pine cape
#

The Controlled component would get unaffected by the ownership transfer

floral meteor
#

hmm... what's the best way to approach this, then? the sequence of events I foresee is:

  • spawning an entity
  • travelling some distance away from it so that the serverside ownership reassignment kicks in and it reassigns to the nearest player
  • I travel back to the entity
  • player 2 leaves, giving me ownership of the entity again

I can add the Controlled component for step 1, but does that mean I'd have to manually remove it for step 2 and readd it for step 4? if so, how do I detect a change in ownership?

pine cape
#

But those entities aren't actually Controlled by any players right? Here you're using it with Lifetime::Persistent and you just want some kind of marker component that indicates who is 'controlling' the entity?

In general, a client has authority over the entity most of the time, unless they disconnect or go too far, in which case the authority is transferred to the server?

floral meteor
#

aha, I might be confusing authority and control again ๐Ÿ˜…

#

in that case, I imagine I'd be looking for HasAuthority instead - do I need to manually manage that?

pine cape
#

aha yeah it's confusing:

  • Controlled is just there to serve as a helper for clients to know which entities they 'control' even though the server has authority over them (server is sending replication updates) (+ so that the server can despawn them when the client disconnects)
  • HasAuthority indicates who is actually sending the replication updates. Note that in a scenario where client 1 has HasAuthority and the server is broadcasting to other players, the server doesn't have HasAuthority because it is just rebroadcasting updates to other players.
#

yes i think HasAuthority might be a good choice here, and no it's entirely managed in lightyear

floral meteor
#

hmm, I'm not seeing it in my ECS dump of my locally-spawned entity, and I can confirm the server sets authority: AuthorityPeer::Client(*client_id)

pine cape
#

(also if a client has authority, you could leave the ReplicationTarget to NetworkTarget::All on the server because the server won't send updates to a client that has authority. Or you could still change it to NetworkTarget::AllExceptSingle)

#

But now that you mention it, that seems very misleading. I assume that you were just updating the Replicate.authority directly?

#

I might instead use an observer to handle changes to the AuthorityPeer component

floral meteor
#

I haven't set up authority transfer yet; I'm making sure we have the ownership/authority/control model set up properly before working on authority transfer

#

hmm, how odd, I'm spawning the clientside entity with

Replicate {
    group: ReplicationGroup::new_id(local_client_id.to_bits()),
    ..default()
}

attached, which I presume would also attach the HasAuthority component (as that's part of the bundle)

#

I wonder why it's not showing up, then - as far as I can tell, HasAuthority is a registered type, so it should show up in my reflection-based ECS dump

pine cape
#

I probably didn't register it for reflection

floral meteor
#

nope, it's definitely in the SharedPlugin O_o

#

let me write a quick system to check if the component is actually missing or not

#

okay, yeah, it's there, weird

#

alrighty, that should be sufficient to go on with, then - I'll rebuild around HasAuthority and authority transfer

pine cape
#

Cool, let me know how it goes! I'm pretty happy with the current authority design

floral meteor
#

initial port over seems to work well, we can get rid of our OwnerId component - need to do a bit more testing and actually implement authority transfer but it's looking promising ๐Ÿ™‚

idle pier
#

is there an easy way to debug which component is being replicated or what messages are being sent?

#

iโ€™m getting an insane amount of packets being sent by the server

#

as soon as i run the server without limiting the Update schedule

#

setting the send rate to something like 100ms doesnt seem to do anything

pine cape
#

where are you setting the send rate?

#

It's really confusing right now but the one that matters is ServerConfig.ReplicationConfig.send_interval

idle pier
#

i have a feeling it might be LWIM

pine cape
#

Clients send input packets every frame

#

Hm there's no great GUI to look at which packets were sent right now

#

and then maybe enable debug logs also for server::inputs and client::inputs

idle pier
#

the client receives 120371232178672 input updates per second

pine cape
#

The client isn't supposed to receive input updates, the inputs are sent from the client to the server.
Are you manually re-sending inputs from server to client?

idle pier
#

like in the examples

#

disabling that system doesn't change much either

pine cape
#

Are you sure you're computing your metrics correctly? How could 120371232178672 messages be sent from server to client? (also the server doesn't send input messages if you disable that system)