#lightyear
1 messages ยท Page 5 of 1
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?
Hi, yes it is. What kind of issues
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
The mapped entity already exists on the server when the message is received?
i.e. the entity was already replicated from client to server?
The entity is replicated from server to client, so it definitely exists first in the server. Would the client to server mapping only work if the entity is replicated from the client?
Yes, the flow is:
- replicate entity
Afrom Sender to Receiver. Receiver spawnsA* - Receiver maintains an internal mapping of
A -> A* - then if Sender sends a Mapped message containing
Ato the receiver, the receiver will automatically map it toA*
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)
I see, thank you!
That'd be useful, yea. In my use case all of the entities are owned by the server, but there's plenty of messages being sent from the client to the server referring to entity Ids (mostly related to inventory management)
Out of curiosity, if the reverse mapping (A* -> A) already exists somewhere in the receiver, why not use that with MapEntities to transparently map A* to A?
So:
- sending from sender->receiver, we convert from
A->A*in the receiver? - sending from receiver->sender, we convert from
A*->Ain the receiver?
Yes that's a good point, it's just that it's hard to identify who is the receiver/sender, since thesend_messageAPI is just sending a message between 2 peers
I added a few functions: https://github.com/cBournhonesque/lightyear/pull/554
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
I guess for the time being I could just remove the MapEntities impl from the client-to-server messages, and do the mapping manually before sending.
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
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
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?
hey everyone! i followed the tutorial until initialization and when i run the game, it throws a resource does not exist: lightyear::protocol::component::ComponentRegistry error. am i missing something? my plugin looks like this so far:
Hi, the ClientPlugins/ServerPlugins must be added before you call any register functions
Is it expected that you're adding the Client and Server plugins in the same NetcodePlugin?
oh that was it! ๐ thanks!
yes, i'm planning on running commands.start_server() based on an argument (such as cargo run --headless) or if the player wants a HostServer later on if that makes sense... i guess i could also only add the plugin if such argument is present... but i don't think it makes much of a difference? right? ๐ฌ
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
i see
i'll come back and handle that after i get the bare minimum working then
Yep, seems like they always map at the client, regardless of whether it's sending or receiving
I think that works for replicon because there's only server->client replication.
And I guess you can not implement MapEntities if you want the client to refer to the server entities directly without mapping?
Maybe I need to create 2 traits SendMapEntities and ReceiveMapEntities; i'll think about it
Makes sense. Either way your last PR unblocks my usecase so thanks again for that!
Hm maybe as far as i know aery only uses the ecs and doesnt replace it
idt anyone gave it a try yet, might be worth the attempt ;)
I'm not too familiar with that crate, but yea if it's just normal components it should work
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
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.
Hi, I think it might be missing, let me add an implementation based on https://docs.rs/bevy/latest/bevy/math/struct.Quat.html#method.slerp
A quaternion representing an orientation.
or I think you could also introduce your own
I think this should work: https://github.com/cBournhonesque/lightyear/pull/555
Does your normal transform (without rotation) also feel laggy? that might be because you need visual interpolation for your predicted entity
Thanks you so fast, I wil try it tomorrow
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.
I would probably put them before PropagateTransform but after InterpolationSet::VisualInterpolation in PostUpdate
But why don't you work directly with Transform instead of syncing rotation to transform? Transform has a rotation Quat
Correct, the replication update messages are not reliable. If you update it at the same time as you add the Replicate bundle you shouldn't have any issues though
Just made it public, thanks
what do you mean by this? there is no Disconnected marker component
I mean we can introduce a new component and lightyear insert it when Lifetime is Persistent so the downstream users can easily query for it. Just a suggestion.
I have a Position component like this
pub struct Position {
translation: Vec3,
rotation: Quat,
}
The player is allowed to move,rotate their body horizontally and rotate their head vertically when he is not blocked.
When user is blocked, he is allowed to rotate there head only (both orientation) . So Transform doesnt fit my need
hm i'm not sure there's a clear use-case for it, so for now i'll let users define their own marker components for this situation
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
hi @pine cape Can you check why this simple 3D movement still jitter even with VisualInterpolationPlugin. Just cargo run and move with ASDW and rotate with your mouse around the cube.
https://github.com/notmd/lightyear-jitter
Is the VisualInterpolationPlugin suppose to fix all the jitter or I have to to other things?
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?
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:/...
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.
when you change the update_rate
you also have to change it in lightyear
Configuration that has to be the same between the server and the client.
API documentation for the Rust TickConfig struct in crate lightyear.
hm i tried with a FixedUpdate of 10Hz and yeah it seems like it still jitters, let me check why
I just push the change with 32hz, also adjust the camera to look down, so it more like the youtube video
also why lightyear dont detect the config from bevy Time<Fixed>?
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
Also I just try the release build, still the same
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
Does turn on change detection work? In my game, I manually sync the transform so it definately trigger the change dectection
did you implement an interpolation fn for affine transforms?
no, that should be doable
i don't rly know the diff between transform and affineTransform
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
@storm gulch I pushed a fix: https://github.com/cBournhonesque/lightyear/pull/559
I tested it on your repo, it works well
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",
] }
it work great when I lower the update rate to 10 or 5, but when I set it back to 64, it still jitter
I just tried it at 64Hz and it works for me
here is the 5hz version
you can see the first video is more jitter than the second one.
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
I will try again tomorrow.
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 ๐
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
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
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?
cargo run -- client -c 2
oh derp
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.
no worries, ask if you have any questions or ideas for improvements.
And yeah lightyear definitely helps! We were able to make a multiplayer game in a week for the bevy jam: https://cbournhonesque.itch.io/cyclesio
nice, but I still see the jitter, especially the name ๐
the name of other players seem doesnt have the issue
yeah i see that sometimes; it seems to happen more on web builds. Not sure why
maybe the prediction/visual-interpolation has issues? I've tried to debug it by printing logs but i couldn't see anything wrong
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?
Check if it seems to be changing in a linear manner, and seem to be interpolating between correct values
Ay thats awesome, congrats :o
My brain will blow if I continue debug this stuff ๐ I think I live with it and comeback in later stage of my game haha
Also does the lightyear lwim plugin account this issue? #1034547742262951966 message
I think it may related
no in the latest released version
but in the next version it will
Context
Fixes #252
(see description of the issue in bevyengine/bevy#6183)
It would also fix cBournhonesque/lightyear#349
(I did a write-up here: https://hackmd.io/_TGuaUTnRBeuisvUMr0QoQ?both)
i.e. ...
Nice!!
It seems bevy 0.14.1 break lightyear
nice work on the jam game btw ๐ sad i couldn't participate in the jam this time, been away for most of it. great that you could get a multiplayer game running in just a week
yeah, i'm excited for the next one, i have some ideas for another multiplayer game ๐
https://github.com/Noxime/steamworks-rs/pull/179 oh wow, I actually did not expect this to get merged
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.
Good luck!
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
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
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.
Here's the reproduction, by just moving around and pressing M to send 12 chunks at a time after only a couple presses both the server and client get stuck in error loops.
https://github.com/DarkZek/lightyear/commit/c247608713c7a5f909bee1498af1168b2cc9a4f0
interesting, maybe replication group issue @pine cape?
Oh actually looking at the code it seems hes just using messages
If it helps I did try make a simpler reproduction without the moving box and that worked fine
not sure if messages actually have packet chunking at all
or if they are just sent raw through the channel
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
what's the main difference between lightyear and renet?
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
Makes sense. So perhaps lightyear will look a little smoother moving 3d models around?
moving 3d models around should be perfectly smooth
Actually true, thatโs what I saw in the bike game so that sounds very good. Alright thanks will play around with that.
Dang it ๐ I reproduced on my windows pc as well to make sure it wasn't platform specific. On that pc it happens on the first press of m.
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?
yep
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
Is it something that needs to be mitigated?
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
it's so hard for me to reproduce the error though, it happens really rarely for me
Is there any way I can help with logs or anything? Strange it happening differently between machines
you could try running on this branch: https://github.com/cBournhonesque/lightyear/pull/564
i think you have to be fairly unlucky to run into this
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 ๐
Issue created https://github.com/cBournhonesque/lightyear/issues/565
When sending too many large messages both the client and server can experience forever looping TooLarge errors. Reproduction using the simple_box example https://github.com/DarkZek/lightyear just m...
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?
NVM; I found the answer with some searching #1189344685546811564 message
Only the hosting client needs to be in HostServer mode, but yes all clients should start with both Client and Server plugins so that they can become server at some point
The box jumping off the screen is independent from the TooLarge error.
- The
TooLargeerror 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
EntityUpdatesChannelhas 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
@twilit valley I updated the config to include a bandwidth limit + some priority; you can try it here: https://github.com/cBournhonesque/lightyear/pull/564
let me know what you think. You can see that the chunks are being sent in the background without impacting gameplay
although it's truly mysterious how adding the logs prevent the error from happening in your demo, I really don't understand why
In the video I attached we can still see the pink box actively teleporting around which should be the non replicated data I thought?
Excited to try this after work today ๐
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)
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.
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
That's awesome to hear! Thanks for being so responsive about this issue. Lightyear looks awesome and having that book you wrote about it helps so much.
I'm getting some errors trying to build main after the mtu fix so am not able to test if I still get errors ๐ฆ
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?
Eg at 3s (when I press m) you can see me move try move right but get teleported straight back
but i'm not getting any TooLarge issues ๐ Thank you!
There's no compression built-in, and the rate limiter doesn't work totally reliably currently, I think it's more aggressive than expected
Ah I didn't see that in my test, but that's good to know. What are the settings where you observe this?
I gave input and ping packets infinite priority so that they are ordered first when going through the rate limiter, but I guess there's still a chance that they aren't sent at all?
Maybe input and ping packets should be totally excluded from the rate limiter
I just rebased the big packets branch with the changes from main and upped the CHUNK_SIZE to 16
That would make sense to me
Did you try my branch though? https://github.com/cBournhonesque/lightyear/pull/564
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
Yeah that was the branch I rebased
do you have this change? https://github.com/cBournhonesque/lightyear/pull/564/files#diff-5207bba17257dcfbd6c828352d3fb218c37a8a74e710c66ba221eb3301ab1478R68
I just tried running my branch with CHUNK_SIZE = 16, and the box stays perfectly responsive
that change is not in main, it's just in the cb/debug-big-packets branch
Yeah I was looking at that a bit confused, is it sending the chunks to itself by default?
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
Heres my copy of the branch, I rebased and also removed the debug logs again so I could read my logs https://github.com/DarkZek/lightyear/tree/cb/debug-big-packets . I hope this isn't another 'doesn't happen with logs enabled' problem
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.
I think you could try using Transform in your protocol instead of Position/Rotation
since avian2d keeps Pos/Rotation in sync with Transform anyway
my code uses pos/rot components everywhere atm, so it's not a small change ๐ค
Make sure to use
VisualInterpolateStatus::<Transform> {
// set to true so that Transform changes are applied to GlobalTransform
trigger_change_detection: true,
..default()
}
yeah, i tried that and got the "not part of protocol" error
inside your queries you mean?
I don't think you'd need to change any of your queries
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
Are the meshes/sprites child entities of the parent entity?
some are yes
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)
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)
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
ok
Maybe set trigger_change_detection: true,
since Pos/Rot doesn't change with visual_interpolation by default
maybe avian2d doesn't apply any sync
oh, yes
didn't help. will add logging
player pos dumped in Last schedule:
Position(Vec2(1015.0073, -307.67514))
Transform(Vec2(1016.48627, -305.58356))
GlobalTransform(Vec2(1016.48627, -305.58356))
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
that was a pain to debug: https://github.com/cBournhonesque/lightyear/pull/571 (relevant to anyone using avian in fixedupdate, and visual interpolation)
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
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
hmm, yes I only sync pos to transform, not the other way around.
but yeah this works if you won't update Transform directly
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
@pine cape are there any pros to the #2 interp approach? there is one listed, but it doesn't seem like a pro
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
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
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)
yeah
pushed an update to readd the alt interp methods, and a caveat about syncplugin
wow thanks, that's a really good write-up!
glad i finally tracked it down, hopefully the book will save someone else several hours heh
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?
it is supported; I just didn't add it because I had no need for it but it can be added
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?
it's been a while since I looked at this but IIRC bevy_steamworks is not compatible with lightyear.
it's a pretty minimal plugin anyway and you can probably do without it
thx. I'll try that route.
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
am I supposed to enable avian2d to use avian3d with lightyear or should I just not enable it?
you probably shouldn't enable it
Added avian3d: https://github.com/cBournhonesque/lightyear/pull/572
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
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
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
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
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
Yes, it's case by case basis
So currently we have:
check_rollback: a Predicted entity withPredictedHistory<C>for protocol components marked as predicted. It checks for server updates on the correspondingConfirmedentity; if there is a mismatch we enter rollbackprepare_rollback: for each entity, we reset the state to theConfirmedstate (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 sameReplicationGroup. 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 handlesCorrectionexecute_rollback: run FixedUpdatenticks
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::Fullis rollbackable - for those entities, we add
PredictedbutPredicted.confirmed = None(there is no Confirmed entity) - during
prepare_rollback,- if there is a Confirmed entity, we reset the state of the
ComponentSyncMode::Fullto the Confirmed value - if there is no Confirmed entity, we reset the "Rollbackable" component to the previous state recorded in
PredictionHistory
- if there is a Confirmed entity, we reset the state of the
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?
I have to add a test, but it looks like it doesn't, you're right
alright cool, i thought i messed up somwhere xD
is that something i can help with or are you already working on it?
Sure, it looks like the 2 lines here: https://github.com/cBournhonesque/lightyear/blob/42f2a15be99b0dd40618c45879114c321c6475c6/lightyear/src/client/networking.rs#L357-L359
are missing from the on_connect_host_server below!
Maybe you can add them and test that it works?
sure thing
how would i do that in lightyear?
is there a preferred place for tests, do i add one of those tests in comments?
(first time really writing tests in rust, so bare with me)
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
Actually i already have a HostServerStepper
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
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
yes that confuses me too
thanks for the explanation. something like this would also be useful to rollback the various bits of avian we need for better determinism too right? like CollidingEntities for example
yes
@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?
i think the jist is that the server and client always adds a PreSpawnedPlayerObject to bullets, clients always prespawn their own bullets, and they are only allowed to prespawn other players' bullets if they know they have the inputs for the frame (ie, don't predict a remote player pressed fire)
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.
wow that was fast
I expected having to do workarounds and stuff
this immediately quadrupled my respect for lightyear
aha you should test it first because I couldn't test it myself
I will I will
one thing I will say about lightyear
too much boilerplate for my taste but that is fine
To set up a new project you mean?
yep
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
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
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()
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
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!!!
note that you will need two steam accounts to test it out
it's easier to just avoid steam connections during development
And two seperate devices
This seems like a bug in lightyear?
In host server you added both the ClientPlugin and the ServerPlugin to the same app; and they are both trying to create a Steam client? Just a hypothesis
oh actually you provide your own steam client
@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
Yea I think it can be useful, especially if you want to try several network configurations
think i figured out the problem.
HostStepServer::default() already runs .init(), thus the double registration.
problem is, that the .init() already connects the clients so its too late to listen to the connect event. so i just build it with ::new()
here's the pullrequest: https://github.com/cBournhonesque/lightyear/pull/578
is the "blueprints" pattern (as in replicon) supported by lightyear?
What do you mean by blueprints
do I need to derive reflect and register my components if I want them to be replicated?
no
did you add the components to your protocol?
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?
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?
Maybe link a repository with the full code and I can take a look
sure give me a minute
it is basically a disected version of the avian physics example
but 3d
@pine cape https://git.sr.ht/~asya/lightyear-tmp :3
simply clone
and you see the entity getting spawned on the server?
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);
}
}
yea
hm maybe try running init on the server
only after the server is started
run_if(is_started)
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
are you sure your server starts correctly? do you get any logs?
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
am I supposed to? this feels weird
maybe not, i think i only print logs for webtransport
strange; let me try to run your example
if nothing turns up I might try using the crates.io version
and not the latest master
you forgot this: apps.add_plugins(|| ProtocolPlugin);
also make sure to run it after adding Client or Server plugins
I will create an issue to error if no protocol is registered
it still doesn't work with the run_if btw
no idea why
but it doesn't bother me it works without that
because at Startup you're not connected; yeah that was bad advice
@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
Predictedcomponent. If the entity was not replicated from the server, you will have to insertPredictedyourself to let lightyear know that the entity is on the predicted timeline
very nice! we can use that for some of the avian components that need to be rolled back too. I'll give it a try for managing sound effects
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
Hm that's weird, it should work. I tested it in a unit test and it seemed to work fine
Here's the test: https://github.com/cBournhonesque/lightyear/pull/588
I'll have to Check again when i'm back on my PC, might have messed up somewhere
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
There's the aliases ServerMessageEvent and ClientMessageEvent that might help avoiding this
ooh i see! didnt know about them, very useful!
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
you might be referring to https://github.com/rerun-io/revy
Proof-of-concept time-travel debugger for the Bevy game engine, built using Rerun. - rerun-io/revy
Not directly related to networking but we already track history for rollback so less duplication of efforts maybe
For sure a lot of things can be improvement on debugging UX. Keeping structured logs, etc.
Ooo I didn't know about this one, but ya super useful looking
seems nice especially for animations and stuff
ai too prob
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?
do_something needs to use the resource?
yes :)
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
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
but technically i don't have to pre-create on the client? if the resource gets inserted on the server it should also be inserted on the client?
correct
hm, then i might miss something because its not happening
It should: the receiver inserts the resource if it's missing: https://github.com/cBournhonesque/lightyear/blob/57f69ddd3b891c9d34a6867fd27002b3039c9c84/lightyear/src/shared/replication/resources.rs#L248-L248
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
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
It triggers change detection if you receive an update from the remote for that resource
Are you using Bidirectional or ServerToClient?
ServerToClient
I would check if something is modifying the resource on the server
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
thanks for testing this, have to see where i messed up again. but i genuinely don't have any direct changes
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
@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
did you test in host server mode?
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
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.
i've added asserts for change detection, and the normal test passes without issue
i then copied that test, and switched to the HostServerStepper
it now fails at the first assert after the update, for some reason it doesnt change the resource. not even on the server? maybe something's wrong with the HostServerStepper?
i go to sleep now, will investigate more later...
The BevyStepper adds the ProtocolPlugin which calls register_resource
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)
I've fixed resource replication in host-server mode: https://github.com/cBournhonesque/lightyear/pull/592
(or you could also replicate to all clients except the local client)
Ah i missed that, sorry!
Awesome, i'll have a Look later
yes, that fixed it! thank you :)
Hey thanks, this is great!
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.
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
Okay, thanks!
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?
What would be the goal for this? To reduce CPU usage by not running prediction/inputs systems?
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.)
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
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. ๐
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
@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_localmap, and the sender applies entity-mapping using thelocal_to_remotemap. 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 thelocal_to_remotemap.
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 vialocal_to_remote. When the server receives it, it will map it to E2 viaremote_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
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
i think the idea of connect tokens is that you do the auth step before issuing a token, and have a sidechannel from the server to the auth system. so you can look up the connect token to check it's valid, and the associated username, stored prefs, etc
I'm not using netcode or connect tokens
unless I'm misunderstanding connect tokens, they seem to be a netcode-specific thing
did you see the auth example? https://github.com/cBournhonesque/lightyear/tree/main/examples/auth
it is a netcode thing yeah
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
i've only ever used lightyear with netcode, so not sure what your options are
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
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
i'm not sure if you can disable replication but still be connected, i can see how that would be useful
yeah I think that would be useful
disable replication, inputs, rollback, etc. everything apart from RPCs basically
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
(meant in reply to this message)
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
Does lightyear support adding and removing components?
yes, that will work fine, provided the components are registered in the protocol
Thank youโฆ that is fantastic!
I think I need both the extra bit and separate EntityMapper
The local_to_remote mapper will try to map to remote, if it mapped, it will flip a bit.
The remote_to_local mapper will read from remote; if it sees that the bit was flipped, that means the entity is already local so it just returns it . If it's not, it tries to map it in local_to_remote
Yeah maybe all this is a bit overkill but i know of some people that are using lightyear with client->server replication, so it might matter. (although the entities are originally spawned on the server I think)
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)
@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
aha thank you
and which lerp function do you use for globaltransform?
or do you add it without interpolation_fn and correction_fn
I should provide one, but you can also add a custom interpolation fn that linear interoplates the Translation and Rotation components
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());````
Are you using native or leafwing inputs?
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
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?
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
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
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?
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
Ok, I'll try some more, that narrows it down a bit
(it's set here by the CLI)
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
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
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)
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?
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
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
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?
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
ah that would explain it, thanks again!
@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.
I guess it is. New to bevy, new to rust, new to lightyear. So it's easy to not understand all the valves
oh I'm totally new here as well, so take my input with a grain of salt :D
I tried it in main and it worked. maybe run cargo update ?
just updated, still seems to happen
what version of bevy_egui is yours using?
bevy-inspector-egui = "0.25"
โฏ 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 (*)
i don't want things to break though :p
ok
just fix it to 0.25.1
๐ฅฒ
weird, even after downgrading it's still broken
can you send me your Cargo.lock from the simple_box example?
Just one more thing here. Where do you see the HostServer part of the code going in the future? Sounds a bit hacky and a lot of work to maintain, and only simpler in the very simple scenarios?
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 :))
I'm running that example now, but I don't see a cargo.lock beeing created?
I don't have a cargo.lock either
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
PredictedandInterpolatedcomponents 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)
So that was it! I was going insane from this, my game is just in constant rollback lol spoke too fast, I'm still getting rollbacks on that branch so there must be something wrong with my game
@sonic citrus do you have a fix for that egui thing?
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)
nope, I just reset and pulled from main and the build still fails
neat, i can see that being useful in large worlds, to let clients take temporary ownership of things they are interacting with nearby
Great stuff!
Released a new version: #crates message
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
And you don't want to replicate the hexgrid to avoid wasted bandwidth? instead it's deterministically generated from a seed?
yea i think reacting entity_spawn (or maybe to component_insert of a marker component) would work for this
I have set the hierarchy: ReplicateHierarchy {
recursive: false
} on all Replicate bundles
Otherwise what you could potentially do:
- add the ParentSync component on your child entities in the server
- manually add a mapping from the
server_entityto theclient_entityin either the client or the server. The initial message that transmits the seed could also transmit theserver_entityso 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 inConnectionManager.replication_receiver.remote_entity_map
Then on replication the hierarchy should be updated automatically
cool, I'll try that! Thanks!
@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
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
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
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
@wintry dome I think this should work: https://github.com/cBournhonesque/lightyear/pull/602
does the leafwing feature work with leafwing 0.15
Yes
Just to give an update on the solution I chose here. Since intermingling state with visuals was a bit clunky and made it hard to keep my head on straight, I separated the components into its own hierarchy instead. Much cleaner and simpler (for me) to work with
You mean that you're not using parent/child?
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
Yes, I removed that part entirely and the replicated components are now just gamestate that looks up the entities based on a map.
ah good point, I don't think we clean them up on server shutdown. Let me create an issue for that
@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 ๐
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
.disable::<ColliderHierarchyPlugin>() whats this for?
does avian's collider hierarchy not work with lightyear or somethin?
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
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
Do you have a repo where I can reproduce the error? I've never seen that before
well the avian2d demo works for me ๐ so not sure what could be happening
it would still be helpful to see an exact list of changes
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()
};
yea, i'd have to make a minimal reproduction case
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()
};
The server setup should be using Ipv4Addr::UNSPECIFIED in its server_addr
no
hmmm
should that matter then?
i'll try and get a minimal reproduction case working
and share it ๐
prob not, i was checking this that are different from the example
ahh yea ok
the avian physics demo doesn't compile on main
fixed
figured it out @pine cape
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
oh ok nice
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.
- what is that second thing you can see in front of the camera that seems to copy my movement?
- why is my movement working fine, but my rotation (looking around) not? as far as i can tell, i don't have anything different
- when running physics plugin in
FixedUpdatemy 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
- What do you mean by "thinkg in front of the camera"? the blue line?
- It might have to do with how your inputs are computed + maybe input ordering?
- did you add visual interpolation plugin? Be sure to follow this as well: https://github.com/cBournhonesque/lightyear/pull/599
sorry, was getting a bit late!
- i mean the other capsule you can see in the gif
- i'll look further into it
- missed that, added it now and seems to be better.
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
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).
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
Ive seen this before in 2d, iirc u need to compute the euler value of the angle
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?
hm, not quite sure how to do this.
here is how i'm currently doing the rotation:
let camera_vector = action_state.axis_pair(&PlayerActions::LookAround);
head_rotation.0 = Quat::from_axis_angle(Vec3::X, -camera_vector.y.to_radians());
body_rotation.0 = Quat::from_axis_angle(Vec3::Y, -camera_vector.x.to_radians());
head respective body is just the Rotation
app
.add_plugins((
PhysicsPlugins::new(FixedUpdate)
.build()
.disable::<SyncPlugin>(),
SyncPlugin::new(PostUpdate),
))
.insert_resource(SyncConfig {
transform_to_position: false,
position_to_transform: true,
})
like this you mean?
i'll try some print outs on server and client, good idea
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
Also sync should be before visual interpolation I think? Check with the spaceships example
the sync plugin?
Yea, the syncplugin from avian
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,
});
Also 'change_detection: true' on the VisualInterpolationStatus component
But I'd try to print components values + ticks to debug
yes, i copy pasted that observer system from example
will do
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
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
No, copied the avian3d dynamic character Controller
The Order of Systems should be equal to the ones in the Space ship example, but i'll Check again
Could be that i missed something to replicate.
Thanks for the pointers
Yes, that confuses me too
And yes, i mean rollbacks, sorry :)
if I'm on the main branch of avian and main branch of lightyear, I just have to change all FixedUpdate systems to FixedPostUpdate ?
or should all the input handling etc still happen in fixedupdate
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
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 :(
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.
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
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...
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
what did you do with the removal of .insert_resource(Time::new_with(Physics::fixed_once_hz(FIXED_TIMESTEP_HZ)));, the suggested new code seems specific for FixedPostUpdate
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));
what does the SyncPlugin do? I feel like I haven't seen that before
(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
main ish yeah, recent enough
: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
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
it's also probably possible to just put your logic in FixedUpdate. Just remember now that its inputs in FixedPreUpdate, logic in FixedUpdate, physics in FixedPostUpdate.
(or set physics in FixedUpdate as before)
so if I add tnua, the TnuaController & TnuaAvian3dPlugin also go in FixedPostUpdate, but the actual movement code in FixedPreUpdate + TnuaUserControlsSystemSet?
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)
hm lightyear doesn't check if a leafwing action is disabled?
i disable on the client, and on server its still active
Correct, disabling was reworked recently in leafwing so I didn't add it yet
Here's the issue: https://github.com/cBournhonesque/lightyear/issues/507
As i currently really need this, i'll have a Look at it tomorrow, should be able to do that :)
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?
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
I see
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
Btw are you experiencing this on Main or on the most recent releases? I'm on the current Releases and also have that happen constantly, even when not moving at all
Can't we do it so that the client also checks if the action is disabled and he then just doesnt send an Update?
can you guys invite me to your repo so that i can check what's happening? you shouldn't get any rollbacks
I can surely do that... But my Project is a bit of a mess, be aware XD
It totally could be that i missed something, which is what i'm looking into a bit
I messaged you privately!
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?
you can access the configs as a resource and change them before calling commands.connect_client, so you could have a system like:
fn connect(mut commands: Commands, mut client_config: ResMut<ClientConfig>) {
*client_config = /* Set it to whatever */;
commands.connect_client();
}
in that case, is there a way to make a ClientPlugins without making a ClientConfig?
This example also does that https://github.com/cBournhonesque/lightyear/tree/main/examples/lobby (updates the client/server config at runtime)
since I literally don't have any idea what server or IO layer I'll be using at startup
You can just use ClientConfig::default()
that works, ty!
is there a way to specify an HTTPS address for webtransport instead of a socket addr?
what do you mean? like a url?
yeah
no you have to provide a socket addr
(when calling https under the hood it calls a socket_addr with port 443)
is there a way to just completely disable preprediction?
I'm assuming I'm going to have to modify lightyear's plugins or something
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
well the systems exist and can be abused no?
since they'll just accept the client's data at its word and spawn it
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
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
Yes

(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
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
Only one Entity? As in the Server should have no Player only the client?
only one physics entity replicated
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)
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...
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
Oh, sry, no. I never prespawn. My game is simple... I mean OnEnter(GameState::InGame) if is_host_server
But in which direction is the replication?
have you tested in not host-server mode (e.g. running a separate server and client)
I get some rollbacks with 1 client on the server
I'm still working on a minimal reproduction
in non-host server mode?
yeah I'm running server and client separately
also completely aside, what is an average compilation time in dev mode for any of you ?
Incremental compilation is maybe 1-2 sec
wth am I doing wrong then ๐
how long is it for you?
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
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
it would be quite a lot of change for my project to make it work in non-host server mode lol
so no i have not
but i'm also thinking about making a minimal reproduction
@jade ember even running in client-and-server mode? (client and server in same machine, but different thread)
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
no you need to do something like this: https://github.com/cBournhonesque/lightyear/blob/09ab02f60755e1d10357a83d60c05e1164f3c033/examples/common/src/app.rs#L142
the local client is not Local, it's treated like any Netcode client
i see
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
once I figure out my compilation times I'll send you something :D
this requires too many changes in how i setup everything, i'll try to also get a minimal reproduction ready
Man your docs rule my man
Great physics example and so on you sir deseve a medal
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
Yes you can choose which components you replicate from client->server and which ones you replicate from server->client
oh that would be perfect, is it just the channel direction?
that's a mistake, these bugs should be debug!
yea it's the ChanenlDirection
would that allow me to like
give local client authority over e.g. rotation
and replicate it back to other clients
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
Question what app should contain most game related plugins, should it be server or client?
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
Asset loading, should be mostly on server them?
Only on clients I think, you usually don't need assets on the server
Oh man I should really have started my game with multyplayer now I have a lot of plugins that I need to classify lovely
We built a multiplayer game for bevy jam, maybe that can be helpful: https://github.com/cBournhonesque/jam5
Usually it's 3; i always divide into client/server/shared
Ah yes, I am guessing physics mostly go there
Because of this fancy word I just learned roolback
Yeah I've been integrating lightyear into my project and I think I might go the split route as well
too confusing otherwise
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
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:)
what is autoturret? just curious
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?
a basic NPC that runs on the server, raycasts to find stuff to shoot at, seen here: https://www.youtube.com/watch?v=QLhLO6SfRIU - the logic/rendering etc all in a separate crate
whenever i've had a weird crash related to bytes or network packet ser/deser stuff, it's usually because the server and client didn't register exactly the same components in the same order for the protocol.
i wonder if we can detect that with a checksum that's exchanged after connect, at least in dev mode
@pine cape (and whoever wants to look into it) i've created a minimal reproduction project in which i am getting constantly rollbacks even if not moving / doing any inputs, it runs in host server mode.
https://github.com/zwazel/minimal-repro-lightyear-rollbacks
cc: @royal saddle
as RJ said, for me that also correlates with a difference in protocol things between client and server
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
Usually I see that kind of stuff when the client/server are running different versions or protocol
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?
that looks insane! what are you going to do to optimize it?
only register_component or register_message matter, yes. But if you're sure that everything is correct then it might be a bug
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
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
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
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")
also i'm not sure how optimized lightyear is for many entities. I know that other networking engines (Photon, etc.) try to have 0 runtime allocations but that's not really something i've explored
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..
thanks! I'll give that a shot soon
@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?
I use cargo workspaces
@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
Replicatecomponents get propagated to the child. - BEFORE: I was using the parent's
Entity(1) as theReplicationGroupId - 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
Parentcomponent 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 theChild(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
nice catch, i expect i would have run into that before long too
how do I kick a specific client?
API documentation for the Rust NetServer trait in crate lightyear.
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
it gets spammed indefinitely?
i'll create an issue
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
hm i guess just disabling the logs from this file
related to this crash that I kept getting: on a local server it works fine, but as soon as I bump up latency (either by connecting to a remote server or using tc qdisc), lightyear consistently crashes with this
I tested with 250ms fixed + 50ms jitter using tc qdisc and it crashed almost instantly
What kind of crash error do you get?
I've used the LinkConditioner to add latency up to 2s
it's the same one I replied to in my message
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
So 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
nah, this one
Oh nvm i see
.
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
Do you have a minimum reproducing example?
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
Sure, that's fine
have you tried the lightyear examples with an OS-level conditioner? it might have the same behaviour there
For the latest jam I deployed a game on real servers, there didn't seem to be issues
not sure then, let me try building an example and running it with like 1000ms delay
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
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
Please open issues with any identified problems or ideas for improvement
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
I managed to reproduce the bytes panic with 1000ms latency (not link conditioner)
@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
awesome, thanks for the quick fix!
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
Isnโt this achievable already with run conditions?
True. It's easy to forget though
@pine cape would a feature flagged iyes_perf_ui integration be something you'd be interested in me upstreaming?
That's cool! Maybe this could be added as an example?
sure
although i do think, because lightyear already provides the diagnostics stuff
it would make sense to maybe feature flag it?
i'm not sure iyes_perf works, it takes diagnostics as inputs?
no
you essentially provide your own "entries" to the ui
and the source for values can be anything
I don't know if those kind integrations make more sense as a feature flag or as a lightweight separate crate. Probably the latter?
Aha who made this? https://github.com/cBournhonesque/lightyear/pull/617
this looks awesome!
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
Wow this is really cool
gz to the author
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
to me it seems like you don't have the Serialize feature of avian3d on?
put this in the dependencies of your Cargo.toml:
avian3d = { version = "0.1.1", features = ["serialize"] }
this fixed it. thank you so much. I see now that avian2d had that feature on. 3d didnt.
derp
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
That's correct, the Controlled component is added by the server based on controlled_by and replicated to clients as per the replication_target, so the sending client wouldn't receive it
what would be the easiest way to determine if the local client's replicating a given entity, then?
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?
that should be fine, yeah
will it be removed once ownership transfers away from it?
The Controlled component would get unaffected by the ownership transfer
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?
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?
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?
aha yeah it's confusing:
Controlledis 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)HasAuthorityindicates who is actually sending the replication updates. Note that in a scenario where client 1 hasHasAuthorityand the server is broadcasting to other players, the server doesn't haveHasAuthoritybecause 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
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)
(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)
ah you need to call the https://docs.rs/lightyear/latest/lightyear/prelude/server/trait.AuthorityCommandExt.html#tymethod.transfer_authority command
API documentation for the Rust AuthorityCommandExt trait in crate lightyear.
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
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
I probably didn't register it for reflection
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
Cool, let me know how it goes! I'm pretty happy with the current authority design
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 ๐
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
where are you setting the send rate?
It's really confusing right now but the one that matters is ServerConfig.ReplicationConfig.send_interval
im setting it in both shared and server yea
i have a feeling it might be LWIM
Clients send input packets every frame
Hm there's no great GUI to look at which packets were sent right now
You can try turning debug logs on for shared::replication::receive to see logs like this: https://github.com/cBournhonesque/lightyear/blob/398eec50181d1f671458e80dcad4bf4fc7e4b1bb/lightyear/src/shared/replication/receive.rs#L826-L826 that show all the replication packages
and then maybe enable debug logs also for server::inputs and client::inputs
yea
the client receives 120371232178672 input updates per second
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?
yea but just to other clients
like in the examples
disabling that system doesn't change much either
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)