#lightyear
1 messages · Page 8 of 1
it is native UI, just not updated for 0.15 and not flexible enough when you want loads of metrics
U mean bevy native?
Yes, but i haven't found a good replacement yet
Are you working on cb/0.18? I already started a lot of the example porting work
I wouldn't to duplicate work
My list is here: https://github.com/cBournhonesque/lightyear/pull/700
yep I made a pr onto your branch earlier
rebased on CB/0.18 so no duplicate worki hope
yeah the quat impl has it baked in now
The inputs don't work on avian_3d_character; I'm gonna look at other examples first before coming back to this one
I've released a new version that is compatible with bevy 0.15! #crates message
nice 💪 I'll try and get all the wasm examples hosted soon
Humm, interesting I made it so my transform is visually interpolatable. And I made a simple system that adds a constant vector to my predicted entity. I keep getting correction rollbacks on it, I wonder why
Do you have an example repo?
The logic goes as follows:
First: I register transform in the following manner
app.register_component::<Transform>(ChannelDirection::ServerToClient)
.add_prediction(ComponentSyncMode::Full)
.add_interpolation_fn(TransformLinearInterpolation::lerp)
.add_correction_fn(TransformLinearInterpolation::lerp);
The interpolation being -
pub struct TransformLinearInterpolation;
impl LerpFn<Transform> for TransformLinearInterpolation {
fn lerp(start: &Transform, other: &Transform, t: f32) -> Transform {
let translation = start.translation * (1.0 - t) + other.translation * t;
let rotation = start.rotation.slerp(other.rotation, t);
let scale = start.scale * (1.0 - t) + other.scale * t;
let res = Transform {
translation,
rotation,
scale,
};
trace!(
"position lerp: start: {:?} end: {:?} t: {} res: {:?}",
start,
other,
t,
res
);
res
}
}
Second - I made the following really simple movement system in client both of them in fixedupate set
fn move_player(
mut player_action: Query<
(&ActionState<PlayerActions>, &mut Transform),
(With<Predicted>,With<Controlled>),
>,
) {
for (player_action, mut transform) in player_action.iter_mut() {
// You know only act when we actually have something to do
if !player_action.get_pressed().is_empty() {
// Make this shared
if player_action.pressed(&PlayerActions::Forward) {
transform.translation += Vec3::new(0.0, 0.0, 0.1);
}
}
}
}```
And in server
fn move_player(mut player_action: Query<(&ActionState<PlayerActions>, &mut Transform)>) {
for (player_action, mut transform) in player_action.iter_mut() {
if player_action.pressed(&PlayerActions::Forward) {
transform.translation += Vec3::new(0.0, 0.0, 0.1);
}
}
}```
I also made the whole replicate input to other predicted as follows
app.add_systems(PreUpdate, replicate_inputs.after(MainSet::EmitEvents));
pub fn replicate_inputs(
mut connection: ResMut<ServerConnectionManager>,
mut input_events: ResMut<Events<MessageEvent<InputMessage<PlayerActions>>>>,
) {
for mut event in input_events.drain() {
let client_id = *event.context();
// Optional: do some validation on the inputs to check that there's no cheating
// Inputs for a specific tick should be write *once*. Don't let players change old inputs.
// rebroadcast the input to other clients
connection
.send_message_to_target::<InputChannel, _>(
&mut event.message,
NetworkTarget::AllExceptSingle(client_id),
)
.unwrap()
}
}```
I am just gonna do interpolate fuck predict all
what was the issue?
Well first issue was with my move_player in that guy, I was only moving controlled player
Not all the other predicted players, when they received the input
That was one
ah yes
so it works now? if a player keeps moving right, there should be no rollback since the InputBuffer predicts that the same key will be pressed continuously
There should only be rollbacks when inputs change, and they can be attenuated with correction or input delay
well yes that's expected, because the client was predicting something different from what actually happened
These rollbacks cannot be avoided, but they can be hidden behind animations, etc.
Curious in settings itself, I noticed a input delay ticks, he establishes the Prediction config minimum input delay correct?
This guy right
Configuration to specify how the prediction plugin should behave
Also visual interpolation plugin, is broken. I kept getting no current value errors in trace. He is perhaps the cause of the constant rollbacks in the physics engines example
Currently only minimum_input_delay_ticks works, the other settings are broken
I just tried visual interpolation in the replication_groups example, seems to work
Oh just noticed in replication_groups he applies solely in client
Yep that was it
/// Add the VisualInterpolateStatus component to non-floor entities with
/// component T. Floors don't need to be visually interpolated because we
/// don't expect them to move.
///
/// We query Without<Confirmed> instead of With<Predicted> so that the server's
/// gui will also get some visual interpolation. But we're usually just
/// concerned that the client's Predicted entities get the interpolation
/// treatment.
///
/// Make sure that avian's SyncPlugin is run in PostUpdate in order to
/// incorporate the changes in pos/rot due to visual interpolation. Entities
/// rendered based on transforms will then have transforms based on the visual
/// interpolation.
@pine cape Avian 3d character has a comment that causes constant rollbacks. At least in my example, when I also apply to replication_target everything breaks, in separated server and client mode.
I think I have a fix for the input-delay configurations: https://github.com/cBournhonesque/lightyear/pull/736
A past PR added extra config options for input-delay:
minimum_input_delay_ticks
maximum_input_delay_before_prediction
maximum_predicted_ticks
These are inspired by SnapNet: https://www.snapnet.de...
It should now be possible to have a dynamic input-delay based on the user's RTT
Nice for deployed games
has lightyear already adapted to the avian 0.2.0 release?
yes
hmm i think the disconnect event is not ocurring
Question do you have any methods to accelerate lightyear compile time? Not gonna lie in my current setup is takinga few eons
oh figured it out it seen rust analyzer was resetting my compile times
oh interesting are we suppose to now manage when a client disconnects and connects? I noticed that now when we press the x or ctrl c in my app terminal server doesnt automatically detect that client has disonnected.
No it should work, if you try the examples and close a client window
The server detects the disconnect
Yeah that one it does problem are ctrl c in terminal (SIGNINT), the window type I had to make a little function to detect which I think is expected since I am using simple setup instead of lightyear_common
Server is not detecting that my client no longer is connected when ctrl c no idea why
Did you set the client_timeout_secs in the ClientConfig?
By default it's -1 so the connection doesn't time out even if the server doesn't receive any packets
You see mr peri that is exactly why I mention you on my video you are the goat
Why is commands.start_server() private?
Okay it seems that on latest main its public.
My bad I was on old version.
Hey, I have a question regarding how to define my data model for it to be networked properly.: My game is fully server authoritative and clients only send single messages (occasionally) and locally simulate/predict (and add local logic such as UI etc.).
Now I was wandering whether having (replicated) data that contains Entities is a good approach like:
pub struct StructurePath {
pub waypoints: Vec<Vec3>,
pub width: f32,
pub start: Entity,
pub end: Entity,
}
or
pub struct TroopMovement {
pub paths: Vec<Entity>,
// ...
}
I imagine the the entities (-ids) will be replicated by they might not map to the same entity on the client. I'm new to bevy and lightyear, so it would be great to have some idea if there's any best practices when it comes to things like this.
it's a fine approach – your messages or components that are replicated can implement MapEntities so that entity ids are translated correctly. at least one of the examples in the repo does it i think, and the book shows how too iirc.
This is fine, as rj said entity ids are mapped. Another thing i like to do is split networked components into smaller ones - especially splitting off those parts that change a lot can help with bandwidth since components are only replicated if they changed under regular circumstances
you just need to be aware of the fact that every replicated includes the typeid iirc
(can be ignored if you are using delta compression)
maybe if we have field level replication someday then this will be unnecessary too
hmm curious should animation states be in predicted entities?
I would expect animations to live on the predicted entities, yes
okay, i am intending to do something a bit complicated and need some advice
basically, i want a bevy client to to establish a websocket connection with an actix-web server, which then forwards the actix-ws websocket to a bevy instance running in its own thread (on the server, this is just a lobby) which then puts it into lightyear and thus makes the player join the game
as far as i can tell, most of the work is just implementing the Transport trait for the actix-ws websocket impl, and then somehow (how?) getting that transport into lightyear (as well as have lightyear not listen forn new connections as well because thats not going to happen)
why can't you just use the websocket transport in lightyear?
oowee had to refactor my char customizer because he wasnt really functional or understandable
Finally got a mmorpg character customizer that is mantainavble
400 lines of code for that gotta love bevy animation api
It make be possible to make ClientConnection and ServerConnections hold trait objects dyn NetClient and dyn NetServer instead of enums, so you could pass your actix-ws websocket (or a wrapper around it) as long as it implements NetClient or NetServer
Any clue why I'm getting this error when trying to connect via websockets?
Server:
pub fn start_game_server() {
let server_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 4433);
let server_transport = ServerTransport::WebSocketServer { server_addr };
let config = ServerConfig {
net: vec![NetConfig::Netcode {
config: NetcodeConfig::default(),
io: IoConfig::from_transport(server_transport),
}],
..default()
};
App::new()
.add_plugins((
MinimalPlugins,
StatesPlugin,
ServerPlugins { config },
ProtocolPlugin,
))
.add_systems(Startup, setup)
.add_systems(Update, (on_client_connect, on_client_disconnect));
}
fn setup(mut commands: Commands) {
commands.start_server();
}
Client:
pub struct NetworkPlugin;
impl Plugin for NetworkPlugin {
fn build(&self, app: &mut App) {
let server_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 4433);
let client_transport = ClientTransport::WebSocketClient { server_addr };
let config = ClientConfig {
net: NetConfig::Netcode {
auth: Authentication::Manual {
server_addr,
client_id: 0,
private_key: [0; PRIVATE_KEY_BYTES],
protocol_id: 0,
},
config: NetcodeConfig::default(),
io: IoConfig::from_transport(client_transport),
},
..default()
};
app.add_plugins(ClientPlugins { config })
.add_systems(Startup, setup);
}
}
fn setup(mut commands: Commands) {
commands.connect_client();
}
In the client code, try 127.0.0.1 for the server address, not 0.0.0.0
same issue
mhh okey imma take that into consideration
i had a wack little idea and am now just evaluating how good of an idea it is
Weird, it should totally work. Can you double check?
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4433);
Hm i tried the examples with websocket + wasm, seems to work fine
Maybe try with Chrome
Firefox should work with localhost/127.0.0.1 too though
kinda weird
That's what I did
I've tried with chrome and firefox
And it still just throws a normal connection error?
thats so weird
I'll try the examples and see if they work. Maybe it's something with my PC
my best guess would be firewall stuff?
Yes, I ran the simple_box example, using trunk serve --features=client with websocket and I get the same error
If I run that example with websocket on native it works
wait, the server wasn't running... ok that example does work
ok, my game works too. I forgot to call .run() on my server App... lol
aha cool
I made some fixes to delta-compression, it should now be use-able in a wider range of situations. I added an example for it: https://github.com/cBournhonesque/lightyear/tree/main/examples/delta_compression
In this example, instead of sending the updated PlayerPosition as a Vec2 (8 bytes), we only send the delta between the previous and the new position (2 bytes).
Shouldn't this be >= 2 bytes? The diff could be larger than that, no?
The diff is encoded as 2 i8
So there is a loss of precision
But basically most of the range of f32 is not needed since the diff between 2 positions should be close to 0
Hm but that would depend on gameplay and replication interval 
Whats the issue with encoding the diffs with leb128 so you dont lose that precision? Ig it doesnt really matter in most scenarios but just i8s is pretty limiting imo
This is just in the example
The user can choose how they do the diff
I'm just not sure how to handle situations where the loss of precision causes the server and client components to diverge
So i think there are 3 scenarios:
- the client needs accurate precision (ie rotation) -> send diff as regular f32/64
- the client doesnt need very accurate precision (ie alpha/transparency) -> lose the precision and send as i8/i16
- the clients needs some precision but not a whole lot (ie translation in some cases) -> send as f16 (which would require a nightly build)
There is also the option of vlq / leb128 but the computational cost probably outweighs the bandwidth savings in most cases.
I think the encoding itself is fine, the user can just use whatever scheme they need. But let's say the encoding always loses a bit of precision in the same direction (real delta is 300 but encoded delta is 298). After a while the server and client positions will start diverging. How can we make sure that this does not happen?
one approach is to do the same quantizing on the server right after the physics step
ie reduce the accuracy equally on server just like the network compression will
ah, great idea
Yes that was my point, it just depends on the app in the end, this isnt really smth the lib needs to take care of
Could also just resync after a certain period
when in HostServer mode, do you need to call commands.connect_client() on the server after starting it? is it ok to do it immediately after calling commands.start_server()? if so is there a way to detect that connection? i tried reading from ServerConnectEvent but it doesnt register when it itself connects, only when other clients connect
also checked the ServerConnectionManager but only the other clients are in its connections
am i wrong in thinking it needs to connect to itself?
Yes, you still need to call connect_client in HostServer mode
The Client and Server plugins are still running independently, just in the same world
This should emit a ConnectEvent
it turns out i messed up the config a bit and i was trying to connect to it through Netcode, it was way easier to just use Local for it 😓
thanks for the answer though!
I am trying to run the avian_3d_character example but I am having issues running clients and servers at the same time
cargo run --features=server - creates /debug/avian_3d_character.exe binary and runs it
cargo run --features=client -- -c 1 - tries to overwrite the /debug/avian_3d_character.exe binary, but since the server is running it can not overwrite.
this is giving me this error
error: failed to remove file `C:\dev\lightyear\target\debug\avian_3d_character.exe`
Caused by:
Access is denied. (os error 5)
how are yall running servers and clients at the same time using the features flag?
I had to copy paste the exe, I don't know if there is a better way
Might be that it works on linux or something
Is there an example for auth that works in wasm?
i guess i can just delete the exe manually, i wonder why cargo run isn't able to overwrite in that case
because the exe is running
seems it is because i'm "unlinking" the running exe (allowed in windows) when I click delete and cargo is trying to actually delete the running exe (not allowed in windows)
i looked and there are a couple approaches
- setting target directory with
cargo run --features=client --target-dir=target/client -- -c 1 - adding [[bin]] section in Cargo.toml and setting the binary name and picking the binary using something like
cargo run --features=client --bin client -- -c 1 - using RUSTFLAGS
RUSTFLAGS="--out-dir=target/client" cargo build --features=client
RUSTFLAGS seems to compile from scratch if they get changed so that option kind of sucks. what are other people doing in their projects now?
Hm i'm on mac and I don't encounter this issue; I can run the binary multiple times without issue
No, you would have to use a wasm-compatible websocket client to get the ConnectToken from the server. Maybe web_sys::WebSocket
did we get rid of the way to launch either client or server via a cli param? if that still exists you should just build with --features client,server since then you only have to compile once anyway. in fact i thought the default features included both client and server, in which case building the examples should probably not specify features.. otherwise you are fighting against the building that your code editor is doing when it runs rust-analyzer and you'll get a lot of slow rebuilds because features are changing
eg my editor is building with the default features. if i then run on the cli with --features=server that will require a full rebulid, and then when i save a change in the editor, that will require a full rebuild again (because different features enabled)
ah, hm. looks like neither client nor server are in the default features for some of the examples. i think they both should be, so that the normal way to try stuff is to not specify features. otherwise you get the fighting-with-your-editor slowdown issue i mentioned, and it's just more of a headache in general. (although essential if you need to do headless/wasm/special builds)
hm i didn't know that editor builds trigger a full rebuild
i thought starting the example with just --features is more elegant than having cli options
starting only with the server would require to use --no-default-features then? that's a bit annoying
correct.. editor will use defaults, so headless means using no default features plus server. I think we should optimise for the common case of people just running the examples on a laptop, and keep server and client features in defaults. no reason to only build a client or headless example except for deployment of live demos really (and as an example of how to do it..)
give it a try? I found it really slow to build non default features interleaved with edit+save
I use rust rover so I didn't notice this, but i can try
Actually when i test the examples i almost exclusive run one headless server in one tab, and then 2 exclusive clients in 2 other tabs
I don't people usually try with client-and-server only, because they need to start exclusive-clients to connect to the main server no?
yeah i'd test with multiple tabs too, but ideally with the same featureset so the binary is the same. perhaps vscode works differently to rust rover. vscode seems to just use the normal target dir, and builds with default features when it runs rust-analyzer. which is trodden on when you then build without defaults with just server feature for example
So moving back towards adding cli options to run client, server, gui, etc. ?
Isnt rust rover really unstable? Last time i tried it, it was laggy and had some major bugs..
This just gave me an interesting but kinda unrelated idea; on the web, if u have a headless server bin, we could run this in a webworker to act as a local server while not being bound to the same wasm module and therefore thread as the client.. iirc client-and-server mode already does that but only on native
i think if there's only one of client or server features active, you could not require the cli option. but otherwise yeah cli options and adding server,client to default features would make building examples easier imo
and would eliminate the overwriting .exe problem on windows too, since server and client would all run off same binary
(a problem i wasn't aware of.. never built on windows)
True! The transport code for netcode is not wasm compatible right now though
So the features would mostly be there just for the usecase of edgegap where you need a small binary with no rendering related deps
Would be very easy to glue together though. Its the same onmessage / send api that websockets use.
i think if you use sharedarraybuffer you can even avoid the memcopy of the message
hmm, so maybe we could build the server as a wasm module? the advantage being it would allow us to run server on another thread on web?
pretty much, yes. wasm clients and headless servers was the reason i added the features. can't think of any other benefits just for the examples
yea exactly. im not super sure what the overhead of transferring packets over to another worker is but i think if we'd use sharedarraybuffers then it should be 0 since they dont actually copy anything. That being said it would need to be a bit more sophisticated than the messaging approach since we dont want to run into race conditions
can you talk from other windows/tabs/iframes to a webworker? if so it would allow pure web demos of multiple connected clients
Yes absolutely
neat. i'm a bit behind on "modern" web tech..
could make for a nice multi client demo
its been baseline for like 10 years tho 😂
yea seems reasonable
hehe yeah i am aware of it but never used in anger myself
im trying to send a message, which is ClientToServer only, but i cant seem to get it to recieve...
im in HostServer mode
this is how i registered it:
app.add_channel::<GameChannel>(ChannelSettings {
mode: ChannelMode::UnorderedReliable(ReliableSettings::default()),
..default()
});
app.register_message::<TestMessage>(ChannelDirection::ClientToServer);
this is essentially how im sending the message from the client side:
fn test_message(
mut client: ResMut<ClientConnectionManager>,
) {
if let Err(err) = client.send_message::<GameChannel, _>(&mut TestMessage) {
println!("failed: {:?}", err);
return
};
println!("message sent"); //this prints
}
i recieve the message in the Update schedule like this:
fn recieve_message(
mut ev_message: EventReader<MessageEvent<TestMessage>>,
) {
for _ in ev_message.read() {
println!("message_recieved"); //it never enters here...
}
}
i was able to send a ServerToClient only message just fine, but i cant seem to get this one to work
The MessageEvent in your receive_message system is the server's MessageEvent, right?
hmm apparently im using the shared one
ill try with the server one
oh my god that was it
thank you
very much appreciated
cool! Yeah it's a bit confusing that it's the same name for both the client and server's MessageEvent
but in most cases the client and server code are separate so you just need to do use lightyear::prelude::client::* or lightyear::prelude::server::*
well for me it appears as ServerMessageEvent, and ClientMessageEvent, but because i was using the shared one it was just MessageEvent, i think thats what made it go unnoticed by me
oh
i dont know how to format correctly
oh i forgot that i also reexport them with different names, that's good
What is the recommended method for adding a singleplayer mode to my voxel game? I know that Minecraft launches an internal server when you join a singleplayer world. I've seen that there's a HostServer mode in lightyear, but is this really recommended? The server and client live in the same application, which can create weird behavior that doesn't happen when joining an external server.
@pine cape Mr Peri, aid me. When a camera follows a predicted player, what is the ideal system location? I am getting a very annoying flickerance going on
HostServer can indeed be tricky to get right and you have to be a bit more careful with your filters. I would just have 2 apps run locally in 2 different threads, similarly to what is done in the examples in Separate mode.
https://github.com/cBournhonesque/lightyear/blob/main/examples/common/src/app.rs#L132
Do you have visual interpolation turned on?
The camera should be after TransformPropagate.
You can look at my game jam demo for example: https://github.com/cBournhonesque/jam5/blob/main/client/src/camera/mod.rs
And yes i do use visual interpolation
I dont think that is it, if that were to be the case it would stutter all the time, but is stuttering only in a few app initializations
What do you mean it stutters in a few app initializations? So it stops stuttering after a few seconds?
Not it sometimes stutter when I connect a client and sometimes it doesnt
But the stutter is only at connection time? It doesn't persist afterwards?
Not it persists
but why sometimes it runs okay and sometimes it doesnt
GOD this is cancerous
that means you're missing some system ordering constraint
so half the time the systems are ordered in the correct order, and half the time in the incorrect order
Know of any tool that lets me see the system ordering?
Transform propagatw is the system responsible for actually applying the transform on that frame no? By running after it it means I will just adjust that on the later frame
you might be missing some other system order
I failed at solving it
System ordering bugs are so annoying when using external crates
Oh well guess I will just make my own camera, also your solution wouldnt work with animation systems, as it would look super weird.
Are your rendering systems also after TransfomPropagate?
Could you elaborate?
Bevy animation system runs before transform propagate. If you move the camera after such action it looks kinda clunky, source trust me. When debuggin i tried everything
I don't think it should cause any issues. The camera should be attached to the position of the player after all the frame's updates have been applied (interpolation, transform propagation, animation), etc.
just thought i would share this i finally started my world building although it looks like shit
In this video it looks smooth 🙂
im using HostServer mode, currently when i close the server i call commands.disconnect_client() then commands.stop_server(), but it seems that the entities with the ControlledEntities componet dont get despawned when i do this, and if i start the server and stop once again it spawns more ControlledEntities, same for the other clients
when a client leaves the server the respective entity with ControlledEntities gets despawned, but never otherwise
am i doing something wrong? or missing something?
Perhaps client timeout setting
Or perhaps despawning client bound entities
It's probably a bug. To make sure I understand:
- when a non-local client disconnects, its entities are despawned on the server correctly?
- when the local client disconnects, its entities are not despawned on the server
- when you start/stop the server, it spawns more ControlledEntities for all clients, i.e. the past ControlledEntities don't get despawned?
its not timing out i dont think
when a non-local client disconnects, its entities are despawned on the server correctly?
yes
when the local client disconnects, its entities are not despawned on the server
yes, idk if its important but i do want to mention i disconnect the client and stop the server at the same time
when you start/stop the server, it spawns more ControlledEntities for all clients, i.e. the past ControlledEntities don't get despawned?
yes
Thanks, i'll create an issue for it.
So the issue is mostly happening only for the local-client itself. I'll test it in the case where the disconnection of client/server don't happen at the same time
alright, thanks
@lyric badge I cannot reproduce this. I'm running the simple_box example in host-server mode.
If I disconnect the local client, both the ControlledEntities entity and the player entity get removed
let me try it out
certainly, the example works correctly, im not sure what is happening with my project then
just got back to it, it seems to happen whenever i stop the server, even if i dont disconnect the client on the same frame
if i disconnect the client it despawns the ControlledEntities correctly, so im just gonna delay the server closing for a frame after disconnecting the client, as that seems to work correctly too
Ok I will try to add a test for stopping the server, thanks
Does anyone know how to debug/fix the CI errors? https://github.com/cBournhonesque/lightyear/actions/runs/12614154185/job/35152775599
It's some kind of compilation error
Oh no linker issues, the worst kind of compiler error 💀
Ocurring in bevy too really weird
@pine cape Lets say you have an npc with pathfinding that path being defined by player would you try to predict it
No I would interpolate it or do nothing
@feral rock do you have more information about the log spam you see in lightyear? I cannot reproduce this in simple box. Which platform are you on?
Yeah I will try to gather a repro case
@feral rock I cannot reproduce this with UDP on simple_box on Mac
are you on windows?
this issue: https://github.com/cBournhonesque/lightyear/issues/322 seemed to suggest that this was windows only
Yup this is exactly what I’m seeing
are you on windows?
Yeah
Question i made an entity pointer like thiss
#[derive(Component, Serialize, Deserialize, Clone, Copy)]
pub struct ServerBaseEntity {
server_entity: Entity,
}
impl MapEntities for ServerBaseEntity {
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
self.server_entity = entity_mapper.map_entity(self.server_entity);
}
}```
The next steps to use it is
app.add_map_entities::<ServerBaseEntity>();
+
app.register_component etc
+
add the component to the replicated entity correct?
And also why does it return me an observer
Yes,
app.register_component::<ServerBaseEntity>().add_map_entities();
Then everything should be handled automatically, you can treat like any other component
hahaha, your answer was amusing
why does it return me an observer?
YES
thanks for the confirmation tho
it doesn't return an observer
Do you happen to know what causes it?
@pine cape In egui it is returning the observer type
At least in client
In server is just an entity
Regarding this; on my mac, my info! in the DisconnectEvent never gets called if that helps
So you're on mac, you're using simple_box example with UDP, and you just close the client window.
You don't get info! from DisconnectEvent and you get a log spam?
You should just do
impl MapEntities for ServerBaseEntity {
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
entity_mapper.map_entity(self.server_entity);
}
}
ah, HMMMMMM it stills returns me an observer
actually i was wrong, you first implementation was correct!
I guess the entity is not being mapped correctly, so it is assigned to an observer
I with the entity-mapping api could return an error when the mapping doesn't work
Are you replicating both entities in the same ReplicationGroup?
you need to do this to ensure that the mapping works correctly
I made an issue so that I can handle failing entity mappings correctly: https://github.com/bevyengine/bevy/issues/17193
perhaps i am implementing this wrong
My idea is, store server base entity so later we send base entity to server, when dialogoing with it to ease up the code
I'm pretty sure what you're doing is this:
- replicate E1 and E2 from server to client. E2 has ServerBaseEntity(E1) component
- they are not in the same ReplicationGroup, so E2 can get replicated before E1
- EntityMapping is applied but it's not applied correctly since E1 doesn't exist on client
Instead what you should do is put them in the same ReplicationGroup to guarantee that they are replicated together
i see seens about right
I ran int o a funny bug mr peri
I made a system where a client can have multiple players already spawned when entering the game, I made it so everytime he spawned a child visual and i did a little bit of an animation action. Turns out if you have more than 16 events, in the early stages they die before bevy can consume them cool right?
But that doesnt happen with added or changed queries
figures
I'm not sure i understand the full context, but feel free to open an issue in the repo
it isnt a lightyear issue it is a sort of mine and bevy issue
Question if you wanted a component that points to server entity, how would you do it, like E1 has a component that says E1, is the one who replicates this predicted and confirmed entity
spawn E1, E2 on server. Add a component C(E1) on E2 on the server. Add replication for E1 and E2 on the same replication group.
Then the entity within C will be mapped correctly on the confirmed, predicted, interpolated entities
Wouldnt that point to E2 and not self entity
Ah you want a component on the client that points to the server entity?
You can add C(E2) on E2 then.
But the ReplicationReceiver also has the EntityMap if you want to access the server's mapped entity
Yes,
What do you mean it has entitymap? There is some sort of hashmap type that points my server entities
Res<ConnectionManager>.replication_receiver.remote_entity_map.get_remote(local_client_entity) would give you the server entity
But why do you need access to the server-entity on the client?
Everything is mapped automatically so you should pretty much never need that
I have two subdivision, entities that can be save in my game npc and player
Basically i needed to this because my plsyer has, mini followers that only spawn when he spawns. And well cant let him hack it so entity base validation it is
Figured it out; the client NetcodeConfig doc comment says the default timeout is 3 secs, but the Default impl sets it to -1, causing it to never timeout
The spam from the update handler is annoying but not directly related
Rapier applies all of it physical systems before Transform propagate, I guess that means it wont conflict with visual interpolation
Oh is that the reason? Let me test with an infinite timeout!
It might, the order should be physics>visual interpolation>transform propagate
So i guess i need to set it is physical schedules before visual intepolation, what is the name of set where visual interpolation logic contained?
Probably VisualInterpolationSet
Thanks, i also had a camera following bug guess that running my system after that oughta fix it. Also I guess u reutilized InterpolationSet in that scenario
it seens rapier applies it is physical simulation directly to global transform, I wonder if that breaks the whole visual interpolation logic
But he does have a timestep mode that interpolates between positions
oh I guess that using him means i dotn actually need to interpolate between my rigid body positions
No you can still apply visual interpolation to GlobalTransform
How does one get the IP of a client(_id)?
from the server? I'm not sure that it's possible in main.
You could get this resource: https://docs.rs/lightyear/latest/lightyear/connection/server/struct.ServerConnections.html
then find the right ServerConnection
from there connection.io().unwrap().local_addr()
On the server we allow the use of multiple types of ServerConnection at the same time This resource holds the list of all the ServerConnections, and maps client ids to the index of the server connection in the list
By utilizing a interpolated timestep wouldnt all my troubles just go aways, after all he would just interpolate between his two previous positions, perhaps rollback would break that tho
It's kind of hard to get info about a specific server right now, I might want to put more things in the ECS as @sonic citrus did to make things more convenient to query; still not entirely sure about pros/cons
I'm not sure what you mean. Adding interpolation uses the interpolated timestep
In rapier there is the timestep mode interpolated it seens to interpolate it is rigidbodies automatically, do you think it would be necessary to visual interpolate my predicted player in that scenario?
I'm not sure, I would try both
Yeah, I think that'd be necessary for logging, banning, etc - would love a convenience func so I don't need to iterate two sets
Nonetheless i think globaltransform interpolation would guarantee it
I'm checking event replication that was merged recently. There is no trigger_targets, only global observers for now?
Ah true, you'd want to trigger an Event on a remote entity target?
Ideally yes, but there are no expectations, I'm just trying out what has been implemented.
I created an issue for it
@pine cape Master Peri, I have been working on interact boxes. That make it possible for my player to interact with my npcs and so on. Should those interaction boxes only be simulated in client, I usually attach them as child of player? Just wondering if my design choice is correct they are physical sensor entities
Yes it's probably a client-only concern
What do you think of my lerp function for global transform?
/// Struct for handling GlobalTransform interpolation.
pub struct GlobalTransformLinearInterpolation;
impl LerpFn<GlobalTransform> for GlobalTransformLinearInterpolation {
fn lerp(start: &GlobalTransform, other: &GlobalTransform, t: f32) -> GlobalTransform {
// Decompose the GlobalTransform into translation, rotation, and scale
let (start_translation, start_rotation, start_scale) =
start.to_scale_rotation_translation();
let (end_translation, end_rotation, end_scale) = other.to_scale_rotation_translation();
// Interpolate each component
let translation = start_translation * (1.0 - t) + end_translation * t;
let rotation = start_rotation.slerp(end_rotation, t);
let scale = start_scale * (1.0 - t) + end_scale * t;
let res = GlobalTransform::from(Affine3A::from_scale_rotation_translation(
scale,
rotation,
translation,
));
trace!(
"global transform lerp: start: {:?} end: {:?} t: {} res: {:?}",
start,
other,
t,
res
);
res
}
}
It should work, it looks similar to what I have for transform
It works
But fixed timestep hz is unsettable in rapier
Because of bugg
Wonderfull
I managed to leave it on a stage very similar to avian, but I still get rollbacks on collision
I think it probably is something correlated to the adition of collision forces as sensor that doesnt occur
Are you using pre-prediction? Rollback is broken for pre-predicted entities right now.
Also are all your entities predicted? or only the controlled player?
All npcs are just replicated, interesting enough collision works normally with them, although I have rollbacks I can still "push them" now other players are predicted and their inputs resent in that case things are just insane. Perhaps it would be wise to create a rollback state, solely for the physics engines. Similar to it was done here https://github.com/cscorley/bevy_ggrs_rapier_example/blob/main/src/main.rs.
Although I am not sure if avian would require the same course of action, curious when you made your avian example did it work flawless? When limit testing avian 3d. I managed to actually make it non rollbackable in a few runs with collsiions occuring, but once one ocurred full desync
Gonna be honest i dont think i actually need physics, might just make some self made structs that dialogues better with lightyear, and I have control
hahahaha, truly i give up on this here I go making a very simple interact_box struct
Maybe a naive question, but how is the consistency guaranteed, if the connection is dropping packages or a client falls behind?
Is the full position send periodically? Or is it guaranteed that every delta update will arrive at all clients?
The server keeps receiving acks from the client with the list of packets that the client confirms it has received.
The server keeps sending Deltas from the last acked version of the Component (which we know the client has received)
Oh, did I misunderstand this? I'm trying to log the player's IP connected to a server
Ah, client_server_map is only pub(crate)
Let me add a function, I think that's a good functionality to have
Does that map even contain the actual client address?
nope it contains a map to the NetServer that manages the client
ah
Hmm i get a lot of rollbacks when booting up my game, specially when I added physics and player is reconnectiing is that expected?
I ought to guess it is as player is allocating to the defined position by server
I think it's expected to get an initial rollback
Is room manager an already replicated resource?Oh wait no is exclusive to server
When did the entire Res become pub(crate)?
Recently, because calling this functions by themselves might leave the server in an invalid state. You also need to do some stuff with other resources (ConnectionManager).
Instead these functions are available in commands
oh you mean that the Res itself is not public, my bad
Just changed it back to pub. In general i'm not satisfied with the ergonomics of having to remember to use ResMut<ConnectionManager> for some things, Res<ServerConnections> for others
Yeah
tbh having to be really careful with the imports because things are named the same on client and server is a bit annoying
There are also aliases ServerConnectionManager that you can use
I'm bound on 0.0.0.0 on my server, can't connect over local network hm
The examples don't work for you?
Haven't tried them over network yet
I was typing out a long question, but I just
ed myself lol. While I'm here, thanks for making this crate, I like it!
The avian_3d_character example with the conditioner set as in the example (latency 150, jitter 10, packetloss 0.05) is relatively jittery for me. The conditions seem not ideal, but also not out of the range of normal latency expected in a real world scenario.
Is there something that could be done to make the example feel smoother?
Is that with only one player?
Network conditions shouldn't even matter in this case as everything is predicted
It might be due to these lines
// We change SyncPlugin to PostUpdate, because we want the visually
// interpreted values synced to transform every time, not just when
// Fixed schedule runs.
app.add_plugins(PhysicsPlugins::default().build().disable::<SyncPlugin>())
.add_plugins(SyncPlugin::new(PostUpdate));
VisualInterpolation runs during FixedPostUpdate.
So the flow is:
- FixedPostUpdate:
- run physics and update
Position - store the current tick
Positionvalue in VisualInterpolation
- run physics and update
- PostUpdate
- run PhysicsSync, which syncs the
Positionvalue toTransform
- run PhysicsSync, which syncs the
(we run PhysicsSync in PostUpdate because Position is updated every frame in PostUpdate thanks to the VisualInterpolation plugin)
- maybe there's no guarantee that
PhysicsSyncruns beforeTransformPropagate? - maybe there's no guarantee that
PhysicsSyncruns afterVisualInterpolationinPostUpdate?
but yeah that's definitely a bug!
I have a fix actually, it works well apart from jumps for some reason
Fun factor, I dont do any of those thing in my 3d game
And it is pretty much the same result, everything okay except when dynamic rigidbody collides dynamic rigidbody
Yes that is only one player started up with:
cargo run --features=server,client
that is very good to hear. I also get double jumps (or kind of double jumps)
Just as a side note, if I remove the conditioner, its smooth as butter.
if i'm running a system on the server that runs in the fixed update schedule, is there a lightyear schedule/set i should be using to make sure it's running in the right order?
Nope, it should work!
sweet, ty!
Does anyone know on the avian_3d example why jumping modifies the y coordinate? I though that z coordinate was up/down
Y is up and down in bevy, like vertically up and down
I always come back to this as reference:
If you have time, would be nice to know what the fix is. Was hoping to try to experiment with physics and lightyear this weekend
I have several ideas:
- this system ordering was maybe missing: https://github.com/cBournhonesque/lightyear/pull/796 Although i still the issue happening sometimes so maybe there's something else
- Some component/resource rollback might be missing: https://github.com/Jondolf/avian/issues/478
- Some people have mentioned that having Sleeping enabled in Avian could mess with the rollbacks. I'm thinking about this. One solution could be to disable sleeping, another could be to also update a component's ChangeTicks when we rollback it
We disable the SyncPlugin in FixedPostUpdate and put it in PostUpdate.
That's because the VisualInterpolation plugin updates Position every frame in PostUpdate, and we want the sync from Po...
Collisions, plus physics times will probably need to be rollbackable
When I messed around with rapier I had to pretty much fully rollback the entire physics world
Which mean rollbacking it is ticks too
Disabling sleeping for both cubes seems to not improve it. There still seems so strange missprediction happening
Yes... not sure what is going on. I tried adding rollback for some avian-specific resources like Collisions;
- I see rollback checks failing for AngularVelocity even though AngularVelocity is (0.0, 0.0, 0.0) on both client and server, so maybe something is not written in the history correctly?
- The actual Position values are not the same even during rollback, so some logic is not running the same between Client and Server during rollback
- If I log the position on the server when the client just connected, I get
2025-01-10T23:55:12.252304Z INFO avian_3d_character::shared: Player after physics update is_rollback=false tick=Tick(1141) entity=19v1#4294967315 position=Position(Vec3(2.0, 2.9986029, 0.0)) rotatio
n=Rotation(Quat(0.0, 0.0, 0.0, 1.0)) lv=LinearVelocity(Vec3(0.0, -0.15328127, 0.0)) av=AngularVelocity(Vec3(0.0, 0.0, 0.0))
How come i have a negative linear-velocity? Would that mean that the player character is going down through the floor?
This part is so confusing to me
Perhaps you have gravity enabled continously pushing the character down
Dont forget to disable warm collisions
Hi, I'm not sure if you guys ever resolved the error for the simple_setup example. when i close the client it spams this error message in the server
I'm on windows btw. Cuz I saw you guys mentioned it was platform specific
yes but it doesn't look like it's going down, the capsule is staying on the floor
I think it was due to the NetcodeConfig::timeout. I set it to 3s so hopefully this wouldn't happen anymore
Ok it looks like the constant rollbacks disappear when you disable VisualInterpolation
Oh actually it's because VisualInterpolation was enabled on the server if I enabled gui
Other observation: with other blocks removed (only main player remains), there is no rollback. With the blocks, the rollback only happens on the blocks, not on the controllable player
I was using the simple setup example. Where do I disable viaualinterpolation in?
Oh server
There is no visual interpolation in simple setup; there is not even an entity replicated 😄
Oh were u not talking about the issue I was having?
Nope i'm talking about the rollback issues in avian 3D
And on collision still rollback qhen client collides client?
I might have been inaccurate: the disconnect event didnt get received due to the timeout being -1, but the error still happens for 3 seconds until disconnect, sorry!
Is it possible to access components on the player entity in DisconnectEvent or is that too late? I'm guessing you need to basically maintain your own map of data
not it is possible, you just need to control it is lifetime so dont make him automatically despawn just because of disconnect
What happens to empty rooms in lightyear? Are they automatically deleted?
the actual entity's components seem to be gone by the time I do client_entity(client_id) in DisconnectEvent
Yes
Are these components that you added yourself on the client Entity?
I try to despawn the client entity only at the end of the frame to let you handle it however you want
Yeah, think I fixed it by basically converting it into an observer
If i wanted to make a p2p network with one peer having authority over the others, should I not even create a server, instead just making an authority client file
or should I still use serverconfig and use the server as the authoritative client
I think you'll still need a server
okay thanks
P2p is bit awkward with lightyear, the best solution i could come up with is to have one player be the host and run in host-server mode
There are still a few problems with that tho but you can try to work around them based on your app requirements
thanks
i just spawned a camera for the server, not sure if thatll work tho
Why not just run in client&server mode? (i.e. have 2 apps)
Btw i just added a new feature called visualizer where you can view a plot of a bunch of lightyear-related metrics
would probably work just as well i suppose
thats really useful, thanks
Is this reporting the wrong address for remote clients? I'm just getting the server's SocketAddr from this, not the client address on the remote. Should look like <remote-ip>:<random port number> right?
Hi, im new to lightyear and have a small question. Im try to use the simple_box example but a bit customized and running in a problem. It looks like i can only accept one client ... so when i connect with the first client, im in succesfull and when i now start my other client its in waiting mode, but when i close the connection from the first, the 2nd client will immediate spawn
im running my server and then my 2 clients on the same machine
i dont know if i can provide any usefull information 😄
2025-01-12T14:58:36.190451Z INFO lightyear::server::networking: Server is started.
2025-01-12T14:58:48.274609Z INFO lightyear::server::connection: New connection from id: Netcode(0)
2025-01-12T14:58:48.475133Z INFO multiplayer_template::server: Create entity 10v1#4294967306 for client Netcode(0)
2025-01-12T14:59:39.061889Z ERROR lightyear::connection::netcode::server: server ignored packet: invalid packet: connect token expired
2025-01-12T14:59:39.182870Z ERROR lightyear::connection::netcode::server: server ignored packet: invalid packet: connect token expired
2025-01-12T14:59:39.303706Z ERROR lightyear::connection::netcode::server: server ignored packet: invalid packet: connect token expired
after while when the 2nd client is in connection state i got spammed by the connect token expired
oh you might be right, thanks
Is the second client using a different client id? Do you have an example repo?
hi, when its connected its a different client id yes. There is no client id untill the first client is disconnected
and i didnt found a Event on the client Side
the configs and systems are basically all from the simple_box repo, i have none yet for my small project 😄
ok i reanalysed my code a bit, maybe its only because i tried to merge lightyear with bevy_quickstart, when i remove all the code around it works again ... i dont know, maybe i just need a break 😄
2025-01-12T22:09:02.037289Z ERROR lightyear::client::input::leafwing: received remote player input message for unrecognized entity
2025-01-12T22:09:02.053712Z ERROR lightyear::client::input::leafwing: received remote player input message for unrecognized entity
2025-01-12T22:09:02.071229Z ERROR lightyear::client::input::leafwing: received input message for unrecognized entity entity=400v85#365072220560 diffs=[[], [], []] end_tick=Tick(514)
So I have the following sceario, my players are predicted entities that are managed by a lobbies resource, as they move from one lobby to another while in game I get that little spawn of leafwing being incapable to replicate message for prediction. Which to be honest is expected as I am yet spawning the player in that room, any ideas on how can i get rid of that?
Yes there should be better handling of stopping/pausing input replication: https://github.com/cBournhonesque/lightyear/issues/507
I have an issue for it but nothing is implemented right now
@pine cape Oh thanks for the quick response Mr Peri, also check this out. Lobbies with predicted entites
each day getting close to that godamm rpg state
Took a look at the issue, also the code is a little scary not gonna lie
So I got barebones Client/Server targets running with a WebTransport config. I'm really close getting the client to connect to the server.
Last thing I am trying to do is authenticate the client connection to the server. I'm using Authentication::Manual to get my bearings.
Does the private_key field of Manual need to match the private_key the ServerConfig is using?
yes
@pine cape Thanks - I'm using PrivateKey::load_pemfile() server-side.
Is there a good way to coerce this into Manual.key?
I am currently trying PrivateKey::load_pemfile().secret_der() to get the raw byte array
What is PrivateKey? I don't think it appears in the repo
It's from the wtransport crate
load_pemfiles is to load webtransport certificates.
The Manual.private_key is lightyear specific, it's just a 32 bytes that are used to make sure that the client and server are authorized to connect
it comes from netcode: https://docs.rs/lightyear/latest/lightyear/connection/netcode/type.Key.html
A 32-byte array, used as a key for encrypting and decrypting packets and connect tokens.
Ahhh I see. I am confusing the two.
Thank you
I was trying to load my WebTransport certificate key
Yep that's what i gathered 🙂
@wintry dome I switched examples to be back to 'all features enabled', so you shouldn't have the issue with rust analyzer anymore. Also on windows you would only have one binary
Does it look like to be caused by the non-deterministic behavior of avian, or might it be something else?
cool. i'll get back on the wasm example work this week 🦾
It's the non-deterministic behaviour of avian, mostly on the order of iteration of contact points, if I understand correctly. If you remove the Cuboids there is no rollback at all
Interesting that it results in such different behaviours. Is there a way to resolve this (ordering results or similar?) or would it require a more advanced rework of avian?
I think @young prawn has started looking into it: #1124043933886976171 message
You could try to take a look as well to see what could be the source of this.
I had a previous PR that looked at the order of entities during collisions: https://github.com/Jondolf/avian/pull/480
But I think this is the order of iteration of contacts
Do we need the server side and client side Replicate components to have the same names? If I use them in the same module I need to alias them.
Fair enough, added an alias: https://github.com/cBournhonesque/lightyear/pull/822
😮 That's the fastest I've ever seen anyone... anything
How can I observe, when an message from server was sent to client?
Would it be something like Trigger<MessageEvent<message>>
nope it's not via observers, it's via event readers
for event in reader.read() {
info!("Received message: {:?}", event.message());
}
}```
Actually this seems like a really good candidate for observers..
(at least if your msgs arent sent that often)
are there any examples/documentation/discussion about doing server side lag compensation for interactions between a client’s predicted and interpolated entities? like, the server verifies some physics thing was successful from the client’s pov when the client did the thing
i realize it’s tricky but curious if anyone has taken a stab at it. search here and on gh didn’t turn me up much beyond “yep that’s hard”
There's no implementation/discussion, just this old open issue: https://github.com/cBournhonesque/lightyear/issues/130
- This thread can be helpful: #networking message
- Explanations: https://developer.valvesoftware.com/wiki/Lag_Compensation / https://gabrielgambetta.com/lag-compensation.html
Basically I would expect something like this:
- add LagCompensation component to entities that can be shot at.
- We might need some API to specify which components are required to do lag compensation (bounding boxes? colliders? just transform?) but that's not necessary for a proof of concept
- For all LagCompensation entities, the server maintains a history of where the collider's bounding box was in the last ~300ms. That can be done with this
- When the server receives the input 'Player C shoots gun at tick T', it somehow determines what the interpolation_tick was for client C when the client's time was at tick T. The interpolation_tick will be something like
T - interpolation_delay. Maybe the client can include in the InputMessage what their interpolation tick was at the time of shooting. (and the interpolation_overstep) - The server spawns a Bullet at tick
Twith the informationinterpolation_delay. When it tries to compute collisions with other objects, it will compare collisions with the other players' bounding boxes with a delay ofinterpolation_delay, to match what the client saw on their screen. If that happens, it emits aCollisionEventwhich is the source of truth used by servers/clients to trigger hit animations, etc.
if other players are interpolated in the past, and current player is in the present, we need lag compensation to register hits I believe that lag compensation only applies for very fast projectiles...
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
Yes I know but observer can observe event perhaps making them triggerable would be nice
I think in this case event readers might be better because there is a specific schedule where events are read (we first receive all packets, and then buffer all events in EventReaders) so there is no real need for observers
peeeerfect. i appreciate the detailed reply. stoked lightyear already supports a history buffer. i will take a stab at it!
Awesome, let me know if you need any help! And yes the history buffer is used in both prediction/interpolation already
Hmm triggers do work with them interestingly hahahha
@pine cape Mr Peri my fully fledged customizer, now has differentiated skeletons similar to games such as warframe
What I might be missing to have components updates coming from a server?
I have the component registered:
app.register_component::<PlayerStats>(ChannelDirection::ServerToClient)
.add_prediction(ComponentSyncMode::Simple)
.add_interpolation(ComponentSyncMode::Simple);
I insert the Replicate component on a server (I don't do it on the client end):
entity_commands.insert(Replicate {
sync: SyncTarget {
prediction: NetworkTarget::Single(player_id.client_id),
interpolation: NetworkTarget::AllExceptSingle(player_id.client_id),
},
controlled_by: ControlledBy {
target: NetworkTarget::Single(player_id.client_id),
lifetime: Lifetime::SessionBased,
},
..default()
});
I don't pre-spawn entities.
I'm sure that component gets modified on the server, but I just don't see it updated on any client... it doesn't matter if I'm inspecting a controlled entity or not, of other player or not, predicted/interpolated/confirmed - I can't see the new values anywhere
The component itself does get created (as in I don't insert it manually, but it actually comes from the server), but it's just never updated
I've also tried modifying the values on the client end as well, they were expectedly written to the Predicted entity
but when I changed component sync mode to Full, the updates would be reset to the default value immediately on the next frame (expected, I guess, if there are no updates from the server?)
app.register_component::<PlayerStats>(ChannelDirection::ServerToClient)
.add_prediction(ComponentSyncMode::Full)
.add_interpolation(ComponentSyncMode::Full)
.add_interpolation_fn(|s, o, t| {
dbg!((s, o, t));
o.clone()
});
speaking of configs, besides auth and transport configs, this is the only thing that differs from the default one:
pub fn shared_config() -> SharedConfig {
SharedConfig {
tick: TickConfig {
tick_duration: Duration::from_secs_f64(1.0 / 64.0),
},
mode: Mode::Separate,
..default()
}
}
it feels like I'm missing something really stupid
Do you add your protocol after the client/server plugins have been added?
This is not fully expected; you should get a rollback only when you receive an update from the server for that actual entity, and you're saying that you're not receiving updates
Do you have a repo link?
yes
it's closed atm, can I invite you? (cBournhonesque, right?)
yes
I would add this log plugin to debug:
LogPlugin {
level: Level::INFO,
filter: "wgpu=error,bevy_render=info,bevy_ecs=warn,bevy_time=warn,lightyear::shared::replication=debug".to_string(),
..default()
}
First I see that you keep receiving constant updates; do you have an entity that keeps being changed on the server?
Could those be leafwing inputs?
No, they were EntityUpdateMessage which are specifically a ComponentUpdate getting replicated
@pine cape I can confirm that the issue is fixed in https://github.com/cBournhonesque/lightyear/pull/764
with a77026d6ede36e400ad453435304128da4a1a7db replication works, with 1a6d9d9dacbdbd6eaa3c1060bd0e5683d33aa9f0 it doesn't
awesome, thanks
Question, why does relevance manager requires having pre existing replication targets?
Wouldnt that add the overhead of having to se them to a bigger target than they should be
Like in my mind, if I add a client id to a room it should just extend it is replication target I might be misunderstanding something
I think if you are using interest-management, you can just set ReplicationTarget to All, because the entity will only be replicated to clients in the same room anyway
So I have an interesting pickle I found myself in
/// When joining lobby make the required actions
fn handle_join_event(
mut join_event: EventReader<MessageEvent<JoinLobby>>,
mut lobbies: ResMut<Lobbies>,
mut rooms: ResMut<RoomManager>,
owned_map: Res<OwnedEntitiesMap>,
) {
for event in join_event.read() {
let moving_client: ClientId = event.context;
let old_lobby_id = &event.message().old_lobby;
let new_lobby_id = &event.message().lobby;
info!(
"Received join lobby {} event from client {}",
new_lobby_id, moving_client
);
lobbies.add_client(moving_client, *new_lobby_id);
if let Some(new_lobby) = lobbies.list.get(*new_lobby_id) {
let room_id = RoomId(new_lobby.owner.to_bits());
if new_lobby.owner.ne(&moving_client) {
info!("Adding this client to room {}", moving_client);
let owned_entities = owned_map.map.get(&moving_client).unwrap();
rooms.add_client(moving_client, room_id);
rooms.add_entity(owned_entities.player, room_id);
}
}
lobbies.remove_client(moving_client, *old_lobby_id);
if let Some(old_lobby) = lobbies.list.get(*old_lobby_id) {
let room_id = RoomId(old_lobby.owner.to_bits());
let leaving_client_entities = owned_map.map.get(&moving_client).unwrap();
if old_lobby.owner.ne(&moving_client) {
rooms.remove_client(moving_client, room_id);
for entity in leaving_client_entities.iter_entities() {
rooms.remove_entity(entity, room_id);
}
} else {
rooms.remove_client(moving_client, room_id);
// rooms.remove_entity(leaving_client_entities.player, room_id);
// rooms.remove_entity(leaving_client_entities.npc, room_id);
}
}
}
}```
So here is my code
The idea here is quite simple, a client has a bunch of entities that he "owns"( is able to customize and so on). For sake of simplicity, these entities currently are npc and player.
Every client when connecting owns a room, the room id always being his client id to bit.
The scenario I am handling is when a client wants to move to another room. He should still be replicating a few of his owned entities in the room he owns, that entity being NPC. Because if someone enters the room he owns, people can still see his NPC.
Thing is when a client is in two rooms, his entitites still get replicated to him.
How in tarnation I can make it so it doesnt do that?
oh I had to remove client from it and only remove player
oh my this truly sucked
You mean that client, player, and npc are in room 1, and then you want to move client and player to room 2, but leave npc to room 1?
Maybe I should provide a move_room function?
I see it seens you brain resuming skills are better than mine
Perhaps
Outta curiosity why should I reinsert client and remove it if I dont want to replicate to self, when he leaves that room?
What do you mean by reinsert client and remove it?
The only guarantee is that you replicating to entities to clients that are in the same room as them
For example, if a client id in both room 1 and 2 but not his entity, he still receives entity from room 2 unless I remove his client from room
So the player entity is not in room 1 and 2, but you still receive updates from that entity? that's a bug
When the entity left room 2 it should be despawned on the client
yeah that is pretty much it unless i remove it is client
Is like thi Mr peri, let say I remove entity one from room, if I dont remove client id, she still gets replicated to that client if he is self @pine cape
This is in host-server mode?
No, separated server
I don't know I have a unit test for this : https://github.com/cBournhonesque/lightyear/blob/18a6228ff13bbbf795046da4a25376a6d6a490bc/lightyear/src/server/relevance/room.rs#L387
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
hmmmm
Well I am gonna try to simulate it later perhaps issue lies with me
@pine cape Question, if I change a server entity, after what system does it is confirmed state get updated? And therefore it is predicted?
Entities are replicated after MainSet::Receive in PreUpdate, and the components will be synced to the predicted entity after PredictionSet::SpawnHistory in PreUpdate
thanks
After about 10 seconds after starting the game, the client seems to run the FixedPreUpdate schedule twice every tick. Was this a known issue on main? I'm on e6147de.
2025-01-22T18:33:47.075693Z SERVER INFO server::pc: Moving PCs 0.1
2025-01-22T18:33:47.162019Z CLIENT INFO client::pc: Moving PCs 0.1
2025-01-22T18:33:47.176455Z SERVER INFO server::pc: Moving PCs 0.1
2025-01-22T18:33:47.262925Z CLIENT INFO client::pc: Moving PCs 0.1
2025-01-22T18:33:47.276766Z SERVER INFO server::pc: Moving PCs 0.1
2025-01-22T18:33:47.295735Z CLIENT INFO client::pc: Moving PCs 0.1
2025-01-22T18:33:47.362326Z CLIENT INFO client::pc: Moving PCs 0.1
2025-01-22T18:33:47.374968Z SERVER INFO server::pc: Moving PCs 0.1
2025-01-22T18:33:47.395614Z CLIENT INFO client::pc: Moving PCs 0.1
2025-01-22T18:33:47.462170Z CLIENT INFO client::pc: Moving PCs 0.1
The 0.1 is the timestep, so it's running multiple times despite the timestep being the same
The FixedMain schedule might run multiple times in a frame depending on your framerate
But it starts to consistently run it twice every time the server runs it
You're running with 2 apps in the same process?
No, they're separate binaries
Does the server have rendering?
No
Then the frame rate of the client and server will be very different (the server will run the whole schedule more often than the client, and it will comparatively run the FixedUpdate schedule less frequently to compensate)
But the issue isn't that the client is running FixedMain comparatively more often than Main, it's running it more often in absolute terms. It starts running it exactly twenty times per second instead of ten times per second
Are you sure that's the case? You might be getting constant rollbacks, which means the FixedMain schedule is running agin
Oh, rollbacks run FixedMain again?
So I noticed this issue bc my player suddenly starts moving more quickly after some time passes, after I changed the client to have authority over the player. So my hunch is that it starts rolling back every frame for some reason, and it doesn't handle rollback of entities with client authority correctly
(I should have said FixedUpdate here. I got the input buffering and input resolution mixed up)
Can you provide a bit more context on your use-case? Do you spawn directly the entity on the client with authority? Or do you spawn it on the server first and then transfer authority? Does the entity have the 'Predicted' component?
I spawn on the client. I think the client_authority example does it the same way
*client_replication, my bad
That example uses PrePrediction, which is slightly different. The entity is spawned on the client in the predicted timeline but the authority is transferred to the server right away
I mean it spawns the player like the cursor in that example (https://github.com/cBournhonesque/lightyear/blob/main/examples/client_replication/src/client.rs#L54)
Sorry if I'm unclear at times, I'm still wrapping my brain around all the concepts lol
I see. So your entity is not predicted at all right?
Maybe the server predicts it, but I don't think it does
I think you're right that in the case of a rollback, client-authoritative entities won't be rolledback at all
Do you have a repo I could look at?
You can add lightyear::client::prediction=debug to your LogPlugin to check if you have rollbacks
Ok. Maybe I could work around it by detecting if a rollback occurred, and not resolve inputs for the player if it is. Is there a way to detect whether I'm in a rollback?
I can add you to my repo if you like
There is, but I think we should try to clarify the situation a bit more. If there is no server-owned predicted entity there shouldn't be any rollbacks
Alright, I invited you
The input resolution is in {client, lib, server}::pc::move_pcs
I'm using a custom log plugin that's based on Bevy's.
If you modify default_filter in there, that'll change the log filtering
let's chat via DMs
Ok 👍
Hi, I'm new to this crate. I have a situation where sending a message via NetworkTarget::Single(client_id) is not working correctly. However, when I use NetworkTarget::Only(vec![client_id]), it sends as expected. Should I open an issue? Or is there something I'm missing?
I have never seen this, please open an issue!
Created an issue: https://github.com/cBournhonesque/lightyear/issues/835. I can try create a MRE once I have some time :)
What was the setup? Were you running in host server mode?
Host and server as separate apps
I'm still using lightyear for bevy 0.14, is there a bug where i despawn an entity from the server, similar entities with the same archetype would also disappear in the client?
ahh nvm, noticed that was not the case, but somehow my blenvy spawned items became non visible..
found the issue, it's my issue, ignore me pls 🤣
Mr Peri, is there a way to validate those send_messages_to_target in server? If so mind telling me how?
What do you mean by validate?
Let say a client sends a message to another foo, he should be able to send that message foo 7 times in one single frame. Is there a way I can check out such factors, like a event that is triggered when that occurs or something.
On the sender side or the receiving side? There is just the MessageEvent event sender/receiver
Well it says we first send to server, and them to other clients. Does that mean that server can event read such messages?
The server always receives all messages, everything packet goes through the server
looking at the code, I see there's the big_messages feature flag which unlocks u16::MAX message fragments. But does this have to be locked behind a feature flag? if you use a varint, could you get up to u64::MAX fragments with minimal protocol overhead?
leb128 always uses one bit more, which can matter in some contexts, other than that i agree, it should be an option
how come the client's NetConfig::Netcode needs an ip and port for the io and auth?
the auth is for the auth ofc, but does that mean that the actual communication happens with the io address and port?
is it fine to have both be the same?
Still on e6147de, it looks like incorrectly predicted components aren't being removed or something. I'm getting this log
2025-01-23T18:42:39.659518Z CLIENT ERROR seldom_state::machine: 124v2#8589934716 is in multiple states: lib::ai::Idle and lib::ai::Follow, but the confirmed entity doesn't have Follow even though I'm predicting both components:
app.register_component::<Idle>(ChannelDirection::ServerToClient)
.add_prediction(ComponentSyncMode::Simple);
// ...
app.register_component::<Follow>(ChannelDirection::ServerToClient)
.add_prediction(ComponentSyncMode::Full);
Oh nvm, I just had to make them both ComponentSyncMode::Full
That might be possible, but it might use one extra byte. Using more than 256 fragments for a single message hould be super rare anyway
My use case is using a ton of fragments, so I guess it's not that rare
not sure why it would use an extra byte though?
only if you have more than 128 fragments
Yes it's fine if they are the same in both io and auth
Hm you're right, i'll switch to a varint
alright, thanks!
@pine cape Mr Peri, I want to get your opinion on something, I want to make a "window" that show a player in one lobby, what is happening on a different one. What would you recommend?
I've registered a message like this:
impl Plugin for MyProtocolPlugin {
fn build(&self, app: &mut App) {
app.register_message::<MyMessage>(ChannelDirection::Bidirectional);
}
}
When I access the MessageEvents on the client using an EventReader:
EventReader<MessageEvent<MyMessage>>
everything works as expected, but when I access it on the server, I get a panic on startup:
could not access system parameter Res<Events<MessageEvent<MyMessage>>>
MyProtocolPlugin is definitely included on both the server and client apps. Is this a bug?
make sure that u are using the correct module path lightyear::prelude::client and lightyear::prelude::server
@primal cargo
Oops, thanks! I was using the lightyear::shared::components::MessageEvent import 😅
That import seems to work fine for the client app
oo, i haven't really used that before, maype Peri can explain how that works haha
I believe there is also an alias for client/server: ClientMessageEvent and ServerMessageEvent
so u can just stick with importing juz the prelude
It works fine on the client because incidentally the ClientMessageEvent is the same as the shared::MessageEvent
Ah that makes sense, I think I'll use ClientMessageEvent and ServerMessageEvent to be more explicit
actually the ServerMessageEvent is also equal to the shared::MessageEvent in main
are you in main or in the latest released version?
Maybe create a specific room R for the 'portal':
- add your client 1 in lobby 1 in room R
- add all entities from lobby 2 that should be visible through the portal in room R
then those entities will be replicated to client 1
assuming the two lobbies are handled in the same server World
I'm using the latest released version, ServerMessageEvent works fine
HUMMMM
but them the result of the portal, is going to be on ROOM r not lobby 1 right?
Or you could add entities from lobby 2 that should be visible through the portal directly in the room for lobby 1
@pine cape Mr Peri, how can I grab eventreader serverconnectevent via world?
let connect_event = world.get_resource::<Events<ServerConnectEvent>>(); is it something like this?
yes
Any ideas if I can read it?
this is more like a general bevy question, maybe you can ask in general/ecs https://docs.rs/bevy/latest/bevy/ecs/event/struct.Events.html
An event collection that represents the events that occurred within the last two Events::update calls. Events can be written to using an EventWriter and are typically cheaply read using an EventReader.
yeah you are right
hello @pine cape i filed a quick issue (https://github.com/cBournhonesque/lightyear/issues/840), i've forked the repo for now but i think this should be an easy thing to add whenever you get the chance, thanks
Sure, feel free to contribute a PR as well! I'm not super familiar with the steam code
hello, i am using the simple setup example on github and am trying to get multiple clients to connect to the server. i am able to connect a single client with Key::Default(), but whenever i use a key that is not the default key thte server rejects the token saying its "expired" or something. does anyone know a way to fix
The private key has to match between the server and client, that's for authentication purposes. You shouldn't modify the Key, but instead change the ClientId
https://github.com/cBournhonesque/lightyear/pull/841
The change is super minor, up to you on exactly how the method names and comments should be written.
If you can init() on the SteamAPI, it will automatically determine the AppId, otherwise you can call init_app() and provide an explicit AppId.
https://docs.rs/steamworks/latest/steamworks/struct.Cl...
should an ClientDisconnectEvent fire when a commands.connect_client() fails for some external reason? i have an observer triggered by it that works fine if, say, the connection times out. but error-driven failures (like OS says you aren't allowed to connect on that port, for example) go unreported
do i just poll the state of the resource instead of relying on the events? or am i missing something
edit: ooo. maybe bc the state never actually changes. i see the comment in there about making it synchronous, that would also be fine
You're saying that in those 2 situations:
- client tries to connect, but can't (port already used)
- client is already connected, but some io error happens
we would like to send aClientDisconnectEvent?
I think 2) should already be handled; because the on_disconnect function (that runs when we enter the Disconnected state) sends the ClientDisconnectEvent. Let me look into 1)
that’s right
I think it's a valid ask, i'm not 100% sure that DisconnectEvent is appropriate here vs something like ConnectionFailure event, but let's just start with that! Fixed with https://github.com/cBournhonesque/lightyear/pull/844
thank you for the quick fix!
this works, but the other disconnect events also get .triggered; should it happen here too?
Oh you're right
The issue is that the DisconnectEvent contains the ConnectionError which is not Clone, so i can't clone it to both trigger it and buffer it in Events...
i think you already omit the inner error somewhere else. if you did that here, could the user just consume the error from the state of the client connection resource when responding to the trigger?
I don't really want to expose the fact that the error is stored in the ClientConnectionResource, it's more of an implementation detail
I will just do the same thing and omit the reason for now
just pushed
i will not rely on it then 😅 all good for my needs though; appreciate it
i feel so bad pinging you an extra time but you don't have a plain commands reference there, that fn works with world
no rush if you are busy, or i can take a stab if you need 🙂
https://github.com/cBournhonesque/lightyear/pull/845
was 1 other thing going on with your removal of DisconnectReason, just did what looked sensible
oops sorry i pushed too soon; thanks this looks great 🙂
hmm, it seems like i have to call disconnect_client() after a disconnect client event before i can try to connect again
behavior is
-> socket already in use error, disconnect fires
-> attempt to reconnect
-> hangs forever
if i manually disconnect the client in response to the event, it works. i guess its missing the state transition in this case?
You're right!
I should have more unit tests for this, but they are fairly tedious to write
world.resource_mut::<ClientConnection>()
.disconnect_reason = Some(e);
world
.resource_mut::<NextState<NetworkingState>>()
.set(NetworkingState::Disconnected);
just centralize the event handling at the state transition, maybe?
yep that's perfect actually
thanks that's much better
I think I found a weird bug
THUNK
@pine cape Mr Peri, I have dynamic scenes that are being loaded unto server (they are save files of entire "worlds" managed by player). How would I go about registering those entities in the remoteentitymap?
As they are pre loaded from the dynamic scene file, I think he is not mapping them
Can you be more specific?
You're loading a serialized scene file into the server? Was the file generated by the client?
If you're just loading a scene into the server and then replicating it to other clients, everything should work fine since those entities are just treated as if they were created originally on the server
I'm having a issue, where every time, that i close my client, i'm receiving the following error
2025-01-27T01:28:43.518412Z INFO lightyear::server::connection: New connection from id: Netcode(0)
2025-01-27T01:28:43.719177Z INFO server: Client 0 connected
2025-01-27T01:28:49.138120Z ERROR lightyear::server::networking: Error updating netcode server: Netcode(Transport(Io(Os { code: 10054, kind: ConnectionReset, message: "An existing connection was forcibly closed by the remote host." })))
and this is never called
fn handle_disconnect_event(trigger: Trigger<DisconnectEvent>, client_ids: Res<ClientIds>) {
if let Netcode(client_id) = trigger.event().client_id {
client_ids.0.write().unwrap().remove(&client_id);
info!("Client {} disconnected", client_id);
}
}
Does anyone have any hint of what can be happening? Thanks!
yeah i just started looking at that too. seems like the disconnect logic only runs if one of...
- the server explicitly calls it
- the client times out
- the client sends a disconnect packet
the only indication (before timeout) that the client has actually disconnected here is lost in this swallowed error https://github.com/cBournhonesque/lightyear/blob/bc749d6185ce8751cbf97ec52d8c47de547fcd80/lightyear/src/server/networking.rs#L143-L145
i think the reverse problem exists on the client too, if you alt+f4 the server
Oh, so in fact it's a bug, i thought that for some reason i was missing another observer for network errors :/
Yes I am loading a serialized scene file in server. It was originated in the server a previous but still server, yes it works fine but when you try to add network relevance mode , I get those weird infos and the entity is not replicated.
I will make a mre give me a second
@pine cape Here Mr Peri, https://github.com/Sirmadeira/mre_scene/blob/main/src/server.rs. You can turn on and off, the lobby_yes_or_no flag. Is set to true, so it doesnt work
I just tested it in simple_setup but I actually don't get any error. Which transport are you using?
Do you receive the error many times or once?
I think the solution would be to listen for the actual error type in
https://github.com/cBournhonesque/lightyear/blob/bc749d6185ce8751cbf97ec52d8c47de547fcd80/lightyear/src/server/networking.rs#L143-L145
And switch to Disconnected state if the error is not recoverable
Thanks for the MRE, I tried it but nothing gets replicated because no entity matches that query:
https://github.com/cBournhonesque/mre_scene/blob/main/src/server.rs#L142
(the scene has Parent, not Children)
ah i see the error now. This? 2025-01-27T16:19:20.711877Z INFO lightyear::shared::replication::receive: update for entity that doesn't exist? remote_entity=16v1#4294967312
Yes, I fixed the sample btw
The general idea of it is that, this dynamically saved entity, should be replicated to client. That doesnt occur when, we use rooms, but it does with no interest management. Weirdly in the MRE no, error is given
scene_world
.spawn(ComponentA(2))
.insert(CarrierId(client_id))
.insert(Name::new("Replicated entity"));```
It's independent from the Scene thing; it's because you add the entity/client to the room before the client exists
So the NetworkRelevanceCache considers that the entity has already been replicated to the client
it's a bit niche; normally you shouldn't add clients to the room before they are connected as you shouldn't have guarantees on what their client id is
I will try making it so it adds them only after a client connect event
As always Mr Peri you are right yay
it worked? nice
Yes it worked when he is guaranteed connected
fn add_replicate(
query: Query<(Entity, &CarrierId), With<ComponentA>>,
mut commands: Commands,
mut rooms: ResMut<RoomManager>,
mut lobby_yes_or_no: Local<bool>,
mut event_reader: EventReader<ServerConnectEvent>
) {
for event in event_reader.read(){
for (entity, carrier_id) in query.iter() {
let client_id = carrier_id.0;
*lobby_yes_or_no = true;
if *lobby_yes_or_no {
let room_id = RoomId(client_id.to_bits());
let replicate = Replicate {
target: ReplicationTarget {
target: NetworkTarget::All,
},
relevance_mode: NetworkRelevanceMode::InterestManagement,
..default()
};
rooms.add_client(client_id, room_id);
rooms.add_entity(entity, room_id);
info!(
"Started to replicate entity {} with component A in lobby",
entity
);
commands.entity(entity).insert(replicate);
} else {
let replicate = Replicate {
target: ReplicationTarget {
target: NetworkTarget::All,
},
..default()
};
info!("Started to replicate entity {} with component A", entity);
commands.entity(entity).insert(replicate);
};
}
}
}
**Q: **I just tested it in simple_setup but I actually don't get any error. Which transport are you using?
A: Netcode
**Q: **Do you receive the error many times or once?
**A: **It keeps logging in a infinite loop, even after the timeout.
2025-01-27T16:37:25.422420Z ERROR lightyear::server::networking: Error updating netcode server: Netcode(Transport(Io(Os { code: 10054, kind: ConnectionReset, message: "An existing connection was forcibly closed by the remote host." })))
2025-01-27T16:37:25.423125Z ERROR lightyear::server::networking: Error updating netcode server: Netcode(Transport(Io(Os { code: 10054, kind: ConnectionReset, message: "An existing connection was forcibly closed by the remote host." })))
2025-01-27T16:37:25.522661Z ERROR lightyear::server::networking: Error updating netcode server: Netcode(Transport(Io(Os { code: 10054, kind: ConnectionReset, message: "An existing connection was forcibly closed by the remote host." })))
2025-01-27T16:37:25.523377Z ERROR lightyear::server::networking: Error updating netcode server: Netcode(Transport(Io(Os { code: 10054, kind: ConnectionReset, message: "An existing connection was forcibly closed by the remote host." })))
2025-01-27T16:37:25.623059Z ERROR lightyear::server::networking: Error updating netcode server: Netcode(Transport(Io(Os { code: 10054, kind: ConnectionReset, message: "An existing connection was forcibly closed by the remote host." })))
Process finished with exit code 0
})))
The problem might be me, ive just started with rust, here's a branch with it.
https://github.com/arthurpessoa/bevy_multiplayer/tree/LIGHTYEAR_ISSUE
Contribute to arthurpessoa/bevy_multiplayer development by creating an account on GitHub.
@violet topaz are you brazilian friend?
Which os and which transport? (Udp/WebTransport/etc.)
Windows/Udp for me, i didn't try any other combos. think imma just fix it tho
UDP /Windows 11
Yeah! 🇧🇷
what solution do you have in mind
well i was going to do what you had said, except now that i've looked into it...that Result being thrown away there represents a pass/fail for the entire try_update, all clients. there's no mechanism from right there to identify which individual client had a failure that bubbled. that error is a ConnectionError which doesn't contain anything to id a client, nor could it handle multiple client failures distinctly atm
i am guessing it should be handled somewhere in ./connection/netcode/server ? haven't really sussed out where yet
yeah
this receiver.recv() is what is failing. but as i sought the root cause here, i found a bunch of bubbled (client specific?) errors that could fail in exactly the same way. did you want to handle them all in-place, bubble them up in a collection somehow, or something else?
i am guessing this is exclusive to netcode / doesn't happen on steam, so handling it outside the netserver abstraction feels wrong
like either the error is recoverable and we ignore it / wait for timeout, or it's not and we disconnect the client. returning a big pile of ConnectionError from try_update might just be useless
I assume it might also happen on steam; if there's some kind of error the try_update() in recv_packets() will start failing and showing errors
yes that's what i had in mind; either we log it (if it's possibly a transient error) or we disconnect the client
sooo
try_updateis only fallible for actual sever failures- within
try_update, server impls are responsible for disconnecting clients with critical failures and log / do not bubble those errors?
I was thinking that the errors would be bubbled up to here, and then the disconnection happens here: https://github.com/cBournhonesque/lightyear/blob/bc749d6185ce8751cbf97ec52d8c47de547fcd80/lightyear/src/server/networking.rs#L144
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
what do you imagine the return type for try_update looking like then? cause all your functions are gonna have this “maybe multiple client failures” type
Ah, I was thinking that the ClientId would be added in the ConnectionError, but that sounds a bit unwieldy..
Maybe the best is to disconnect the client directly inside the netcode server code, as you suggested
I was thinking about that.
Is it too bad of an idea, to also expose this error as event?
fn handle_connect_error(trigger: Trigger<ConnectError>, client_ids: Res<ClientIds>)
This could help in usecase when i wanna expose my server metrics (to my prometheus service), or write some security policies, like suspend an ClientId that's abusing something.
I'm also not a fan of the current design where the receive() function returns early on any error, because it seems to be that some packets can get lost that way). Ideally errors would get buffered somewhere else and a separate thread could take care of them, so that the try_update() function can finish even if one message causes an error
the naive answer is to just pass around a vec of (ClientId, ConnectionError) then.
all the io is single threaded / effectively synchronous? so i can just…do that? seems like the path of least resistance for now, if that’s an acceptable solve for you
can follow-up with whatever else that wants to consume those errors easily too then
Stored in the ServerConnection? That should work.
Although 'm not sure if it's easy to determine if the error is recoverable
gonna argue that nobody is seeing much of these error paths right now. i figured for anything that wasn’t obviously critical, it could just be a warning, and we can refine the criticality of them through the wisdom of experience…
edit: and this only refers to if we call disconnect or not; i think we still keep every error around for inspection
What’s the most straightforward way to have the terminal spit out every single thing lightyear is doing? Super verbose mode. All the stats. I got some config to do and I’m not sure if I’m doing it correct
2 ways:
- use the
visualizerfeature (in themainbranch) and you will get extra egui dashboards showing detailed stats about lightyear internals - update the log plugin with
lightyear=trace(or you can be more specific:lightyear::client=trace, etc.)
wait wut I tought client id were unique to a certain client?
yes, but the normally the server would generate a new random client id when a new client connects
which means I would require some sort of username login system?
To correctly map out who that player entity belongs to correct?\
well
ought to be fun to do it but still I will have to do a lot of remaping
combing through here updating all the error handling... is this a bug?
this code adds the connection to the connection cache before trying to encrypt the challenge token. so in the sad path, where encrypting the challenge token fails, your client never gets the challenge token but is still in the connection cache
looks like it! Also it seems weird to me on re-read that I don't bubble most of the errors, for example on ConnectTokenPrivate::read_from at the start
way ahead of you 🙂
I didn't write most of the netcode code, it was adapted from https://github.com/benny-n/netcode/blob/main/src/server.rs
I'm trying to move lightyear to a multicrate architecture like bevy; it would probably help making things more module/self-contained but i'm not sure about the consequences in terms of compilation time
I'll probably push it off to 0.20
oh interesting! the abstractions around netcode did feel kinda funky (or at least, a little fuzzy on a straight read) to me. nothing a little iterative improvement can't fix
to make sure im understanding everything correctly, what I need to do to replicate every entity from the server to the clients are when the server detects a new client connection through the ClientConnect event, it spawns a new client entity where the client entity has the replicate bundle. then am i supposed to add the visual components in the client plugin?
this is the function i made to spawn in players on the client side. ```
fn display_client(
mut materials: ResMut<Assets<ColorMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
mut commands: Commands,
player_query: Query<(Entity, &PlayerBundle)>,
) {
for (entity, player) in player_query.iter() {
println!("Spawn worked");
commands.entity(entity).insert((
Mesh2d(meshes.add(Rectangle::new(50.0, 50.0))),
MeshMaterial2d(materials.add(ColorMaterial::from_color(Color::BLACK))),
Transform::from_xyz(player.position.x, player.position.y, 1.0),
));
}
}
Yes, I would use an observer with something like Trigger<OnAdd, PlayerId> to react the replication of a new player entity on the client side.
You can also use an additional query Query<(), With<Replicated>> to confirm that the entity was spawned via replication
@pine cape There are some cursed things about the error handling. Here is the worst one so far!
To disconnect someone, you either need the ClientId or the SocketAddr, yes? Well, in the specific case where we are getting the problem I was originally trying to solve - the failure of receiver.recv() that we discussed - I don't see any way to get the address in the sad path. The transport implementations are not obligated to inform the caller of .recv() what connection failed in the failure case. So how can the caller disconnect that client?
Hm it seems similar to what i found there: https://github.com/cBournhonesque/lightyear/issues/322#issuecomment-2108675797
Released version 0.19! #crates message
It doesn't make sense for recv to be the problem, though.
2025-01-27T00:04:26.601595Z ERROR lightyear::client::networking: Error updating netcode: netcode error: An existing connection was forcibly closed by the remote host. (os error 10054)
UDP is stateless! What existing connection!? Doesn't pass the smell test.
So I did a little research. What can happen is, the server tries to send a packet, but the client closed the connection on their end. Well Windows doesn't report such errors on send, they get queued to receive.
By updating the UDP send impl to peek the socket on Windows...
impl PacketSender for UdpSocketBuffer {
fn send(&mut self, payload: &[u8], address: &SocketAddr) -> Result<()> {
let socket = self.socket.as_ref().lock().unwrap();
#[cfg(target_os = "windows")]
{
let mut peek_buf = [0u8; 1];
match socket.peek_from(&mut peek_buf) {
Ok(_) => (),
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => (),
Err(e) => return Err(e.into()),
}
}
socket.send_to(payload, address)?;
Ok(())
}
}
We can now access the error in send_packets with a client_id
2025-01-28T02:28:23.663182Z ERROR send_packets: lightyear::server::networking: Error sending packets: netcode error: client_id Netcode(11040662130883646883) client specific transport error An existing connection was forcibly closed by the remote host. (os error 10054)
Awesome, that's a great find!
you're a pretty good investigator 🙂
What are you building with bevy btw?
i love debugging! i am an older (?) professional in non-games
i am just prototyping lately. i wrote some rollback netcode with unity DOTS a long time ago. was gonna port it over to bevy, but seems easier to contribute to something that exists already. happy to skip a lot of the connection plumbing and byte-muckery. i'm a high level kinda guy
think i'm gonna ultimately try to slap together an arpg/moba feeling character, network it, and see if i can make something feel good
Sounds good! trying to network a game is probably the best way to figure out what's missing in lightyear
For example i haven't tried handling animations/sounds with rollback
Have you tested physics with rollbacks as well?
Yes the avian_3d and avian_physics examples work with rollback; they mostly work but there's still a couple causes of non-determinism in avian that cause rollbacks
Are they pretty bad?
They''re not noticeable, it's just annoying that the client is rolling back constantly for no reason
Well that’s good. Does make severely impact cross platform determinism?
I haven't tested it cross-platform but avian has an enhanced-determinism feature that should guarantee cross-platform determinism.
I think the issue here is that the server and client world have a different number of entities which modifies the order that avian iterates through entities for collision-handling
@pine cape for your consideration https://github.com/cBournhonesque/lightyear/pull/854
improves error handling for just netcode server and fixes the windows error spam on client disconnect. left some comments. lmk what you think of doing it like this / if you see something to simplify
i just saw this. i tried using Query<() replicated> and I dont think the client is properly recieving the replicated data. nothing spawns when I use the above section on the client app
The server is properly recieving the clients when they are connected, so i'm not sure what the issue is. this is the function for servers recieving oncoming client connections
pub fn handle_connections(
mut connections: EventReader<ConnectEvent>,
mut map: ResMut<ClientEntityMap>,
mut commands: Commands,
) {
let spawn_range = (1000.0, 1000.0);
let mut rng = rand::thread_rng();
let rand_pos_x = rng.gen_range(-(spawn_range.0 / 2.0)..(spawn_range.0 / 2.0));
let rand_pos_y = rng.gen_range(-(spawn_range.1 / 2.0)..(spawn_range.1 / 2.0));
for connection in connections.read() {
let client_id = connection.client_id;
let replicate = Replicate::default();
let entity = commands.spawn((
PlayerBundle {
client: client_id,
position: Vec2::new(rand_pos_x, rand_pos_y),
},
replicate,
));
// Add a mapping from client id to entity id
map.map_id.insert(client_id, entity.id());
}
}```
as you can see, the server is recieving clients when they connect but for some reason the clients arent recieving the replication bundle from the server. any thoughts? Thanks
- Did you use the bevy_inspector_egui to check that the entity was replicated on the client?
- Does it work with just
Trigger<OnAdd, PlayerId>? What does your observer look like? - is PlayerId in your protocol?
i just used a print statement in the query in the client for playerbundle, and its not eeven querying which tells me the client doesnt have an playerbundle entities spawned. I tried Trigger<OnAdd> but its giving me an error saying I cant use trigger as a system parameter
PlayerID is not in my protocol, I was using PlayerBundle which is just a custom component instead
Do you mean ClientId?
I just assumed PlayerBundle was a bundle, not a component. If you send me a link to your project I can take a look
ill upload it to github. thanks for taking the time
yea sorry for the bad names lol i was following the cheat book loosely
You are missing the PlayerBundle in your protocol
ohh
Might need something like
app.register_component::<PlayerBundle>(ChannelDirection::ServerToClient)
.add_prediction(ComponentSyncMode::Once)
.add_interpolation(ComponentSyncMode::Once);
do i put this in shared?
yes
thanks for the help, ill try that
Thanks! I left some comments
would u mind deleting the github page I dont want people on discord seeing my real name lol
this works btw, ty
using commands made this way less sloppy. very nice! updated https://github.com/cBournhonesque/lightyear/pull/854
only reason i didn't at first is because i assumed for some reason send wasn't a system and it'd annoy the caller. but the send where i needed commands WAS, in fact, a system 🥲
right now i'm just using a local port to host the server, but in the future if i wanted to make an actual game would it just be a matter of switching the server address to the address of a server i'm hosting or would there be any other configuration that i would need to do?
pub const SERVER_ADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5000);
Yes you would just need to change the SERVER_ADDR
thanks
what's the idiomatic way to exclude entities from replicating a replicated component? say i want to replicate Transform once for all my level geometry, but i want full sync for my players' Transform.
i thought i'd just put them in a different replication group, but ReplicationGroup doesn't seem to expose enough for me to selectively control the updates like that.
nothing else on Replicate looked helpful. i could make a SyncTransform component and just copy it back and forth to Transform where i want, but that's kinda gross
If this component is present, we will replicate only the inserts/removals of the component, not the updates (i.e. the component will get only replicated once at entity spawn)
yep works 👍
Alright, I'm switching to having two hostserver mode apps running. One runs start_server(), the other connect.client(), but cannot connect. They both have identical auth, addr (LOCALHOST), shared, etc.. What am I missing in the process?
Same machine, two different apps. Connection attempt times out for client
Do I need the client to reconfigure to a Separate mode to connect to the other hostserver app?
It should be possible to run them both in HostServer. Actually in the lobby example all clients start in HostServer mode
Oh, it looks like there is a client config. The host changes their client's NetConfig to Local, and everyone else goes to NetCode
I see. HostServer must config client to Local, start_server(), then connect themselves with connect_client(). Incoming "remote" clients need to config to NetCode to join
Yep exactly, it might be possible to simplify this
I'll probably going to get rid of replication events such as ComponentInsert, ComponentUpdate, etc.
they don't seem that useful, and are redundant with bevy's own Changed<>, Added<> filters
I think granularity is the way to go. Confusion can always be solved by guidebooks
Wow lightyear progressing at lightspeed atm, wish i had enough time on my hands to check out all the new stuff
congratz on the new releases
aha yea i found a new job which starts in one month so i have a lot of free time now
are you going to work for foresight?
no, it's a quant trading company
imagine having master peri in my company, godamm. A dream would come true :>
Question, how can I utilize the replicate once component, to estabilish client start position?
I only seen to be able to use default alues
it takes the value from the component
// on server
commands.spawn((
Transform::from_translation(player_start)
ReplicateOnceComponent::<Transform>::default(),
))
but you're replicating your player position somehow anyway, yeah? if it's fully sync'd just spawn your client in the right place on the server and replicate as normal
no I am not I am utilizing physics
using my fork with my error handling branch atm and getting a lot of this, which i think was just getting swallowed before:
2025-01-29T23:18:19.088420Z ERROR lightyear::client::networking: Error sending packet: netcode error: address 127.0.0.1:12025 address specific transport error A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself. (os error 10040)
which i'm going to guess is only happening on windows 😅
Hm that's strange, so some packets are not getting sent correctly?
the peek buffer in my change wasn't big enough. and fixing that, for reasons i cannot comprehend, makes the windows-specific bug with client disconnects come back
i think i'm changing my stance on that error even being fatal, too. should just let clients time out normally and suppress the noisy log
Yes but it's hard to suppress that log; or maybe in receive_packet we can also choose to just not log anything for that error?
yeah i mean this works it's just ugly
fn recv_packets(
&mut self,
sender: &mut impl PacketSender,
receiver: &mut impl PacketReceiver,
) -> Result<()> {
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
// process every packet regardless of success/failure
loop {
match receiver.recv() {
Ok(Some((buf, addr))) => {
if let Err(e) = self.recv_packet(buf, now, addr, sender) {
self.handle_client_error(e);
}
}
Ok(None) => break,
Err(e) => {
match &e {
TransportError::Io(io_error) => {
match io_error.kind() {
std::io::ErrorKind::ConnectionReset => {},
_ => {
self.handle_client_error(e.into());
},
}
},
_ => {}
};
}
}
}
Ok(())
}
and then that doesn't extrapolate into a strategy for general log suppression; kinda poorly separates concerns doing log filtering inline, idk.
I can still merge your PR without the windows-specific code for now
im gonna push something up that should be suitable, and eliminates the need for anything windows specific + fixes the log spam + lets clients timeout normally
gimme a bit
sure, take your time
https://github.com/cBournhonesque/lightyear/pull/854/files cleaned up. no windows change for now. sent the suppressed error to debug! just in case it ever matters to somebody.
healthy clients seem to be unaffected during the failed receives due to the windows thing, so i can't find a downside to ignoring it
so in order to display all the "player" entites of clients connecting to the server, i made this display_client function. the problem is I need to call the function using Update (otherwise the function doesnt work properly), and it seems kind of inefficient to be inserting the same components every single update. Is there a way i can get this to execute only after a new client spawns or something?
pub fn display_client(
mut materials: ResMut<Assets<ColorMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
mut commands: Commands,
player_query: Query<(Entity, &PlayerBundle)>,
) {
let query = player_query.iter();
for (entity, player) in query {
commands.entity(entity).insert((
Mesh2d(meshes.add(Rectangle::new(50.0, 50.0))),
MeshMaterial2d(materials.add(ColorMaterial::from_color(player.color))),
Transform::from_xyz(player.position.x, player.position.y, 1.0),
));
}
}
yes you can do Trigger<OnAdd, PlayerBundle> or Query<(Entity, &PlayerBundle), Added<PlayerBundle>>
It can. From what I understand (https://partner.steamgames.com/doc/api/ISteamNetworkingSockets#functions_send_recv)
ConnectByIP will just use normal UDP, but ConnectP2P will send packets through Valve's network.
Actually CreateListenSocketP2P (where the server can receive packets relayed by Valve's network) is not supported yet (https://github.com/cBournhonesque/lightyear/blob/23c78ae54a7818b7ae8fb1e38c0350e427bf01f8/lightyear/src/connection/steam/server.rs#L105-L105) so maybe not
Are all sub replication components registered?
Components such as Replicationtarget group and so on?
Most of them I believe, but I might have missed some
@long marsh just pulled your PR and it's fixed the NetCode error handling, ty vm! 🙂
Now, i'm facing just a minor inconvenience.
- When the client connects, the server pops a lot of warnings about
Client Error: Netcode(ClientNotConnected(Netcode(0)))for a few seconds (While doing a handshake?)
2025-01-30T17:39:51.812779Z WARN lightyear::server::networking: Client Error: Netcode(ClientNotConnected(Netcode(0)))
2025-01-30T17:39:51.813374Z WARN lightyear::server::networking: Client Error: Netcode(ClientNotConnected(Netcode(0)))
...
2025-01-30T17:39:51.819448Z WARN lightyear::server::networking: Client Error: Netcode(ClientNotConnected(Netcode(0)))
2025-01-30T17:39:51.820347Z WARN lightyear::server::networking: Client Error: Netcode(ClientNotConnected(Netcode(0)))
2025-01-30T17:39:51.821183Z WARN lightyear::server::networking: Client Error: Netcode(ClientNotConnected(Netcode(0)))
2025-01-30T17:39:51.822049Z WARN lightyear::server::networking: Client Error: Netcode(ClientNotConnected(Netcode(0)))
2025-01-30T17:39:51.823206Z INFO lightyear::server::connection: New connection from id: Netcode(0)
2025-01-30T17:39:52.023832Z INFO server: Client 0 connected
Again, tyvm! Amazing Changes!
yeah it happens here
the client IS in the connection cache, because they started the handshake, but they aren't connected, because they haven't finished it
i was confused about that function originally - is it JUST sending keepalives? it's inappropriate to error there if that's true
might make sense to just pull the two calls to handle_client_error out of that function
Lobbies + managed room + dynamic scenes loading, noticed how the npc entity changes as to reflect the npc entity of that specific user. Worth noting that the wolrd itself is also user defined.
Cool! Is that black door a portal?
It is, there is another region in each I intend to display a window to
So player can see if someone is spawn camping he
Btw Mr Peri did you manage to figure out what was causing the rollback on collisions using avian?
I didn't investigate, it's some non-determinism in avian
In collision normals?
in how the contact points are processed, i think
oh we got lag compensation now? Cool
Hey @pine cape I created the Mapped entities fails on predicted copy Issue
https://github.com/cBournhonesque/lightyear/issues/859
Happy to elaborate on here if that's easier for you.
I have a predicted entity and a component with mapped entities. If I assign an unpredicted entity to that component, then mapping fails with Failed to map entity ... and assigns the placeholder ent...
Thanks I saw, the thing is that I think mapping entities automatically from Confirmed to Predicted is usually the right move
I could update entity mapping to just not update the entity at all if the entity is missing in the map
I had recently switched to set it to Entity::PLACEHOLDER for easier debugging
Where is that getting set in source?
Can you explain more about your usecase?
i'm curious to understand why you're only only predicted one entity
and you want the component on the predicted entity to map to a confirmed entity
Yeah, I've got a PlayerAvatarComponent on a Predicted entity, then a ThirdPersonCameraComponent on a entity that is not predicted. They're both spawned by the server and then I transfer authority for the camera to the controlled client. The camera forward is replicated to the server from the camera and then I use the forward to point the PlayerAvatar entity in the controller.
Technically I'm only not predicting on the client that controls the camera, the rest predict it and don't have an issue.
The PlayerAvatarComponent is the one with the mapped ThirdPersonCamera.
#[derive(Component, Debug, Reflect, Serialize, Deserialize, Clone, PartialEq)]
#[reflect(Component)]
pub struct PlayerAvatarComponent {
pub player: Option<Entity>,
pub camera: Option<Entity>,
}
impl MapEntities for PlayerAvatarComponent {
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
self.player = self.player.map(|unmapped| entity_mapper.map_entity(unmapped));
self.camera = self.camera.map(|unmapped| entity_mapper.map_entity(unmapped));
}
}
Aha i don't fully understand your use case
but this fixes it: https://github.com/cBournhonesque/lightyear/pull/860
See #859
There might be cases where we don't to map entities automatically between Confirmed and Predicted.
One such case is if we spawn 2 entities C1 and C2, but only one of them is predicted
@pine cape Mr Peri, how do I replicate children entities?
Directly from parent
I was testing it out it seens is a little broken or I might be missing something
Mre sample
children entities are by default automatically replicated
did you push all your changes?
Ah it doesn't work when using interest-management: https://github.com/cBournhonesque/lightyear/issues/765
I can take a look later, it shouldn't be hard
You see Mr Peri our default pic for ui currently is Mr Shatur and you, if you do that. It will only be you 🙂 hohoho
Thanks for the quick fix!
I recently pushed a couple of big PRs that massively change the way replication/prediction are implemented internally. Normally things should work the same, but please let me know if you notice anything weird/broken on main
Trying to debug error spam. Is it valid if I give server and client a LOCALHOST addr, and either 0 or like 7777 as the port? Would that cause problems?
it should work
let me check it on windows, just in case its macOS
Yeah no, I'm stumped. @pine cape Would you mind taking a look?
what's not working?
Just connectivity, server-client with multiple apps
I'm sure I messed up or forgot a setting somewhere
yes i would try to check what you have that's different from https://github.com/cBournhonesque/lightyear/blob/main/examples/simple_setup/src/server.rs
Ah of course! I had the hostserver configs at Mode::HostServer, but the separate client was also on Mode::HostServer . Needed to be on Mode::Separate
Don't know if intended or bug:
Disconnecting a hostserver mode local client appears to despawn all locally owned entities with ClientReplicate, too. Should it do that?
no, but it looks like easy to add; there's just one function missing in https://github.com/cBournhonesque/lightyear/blob/23c78ae54a7818b7ae8fb1e38c0350e427bf01f8/lightyear/src/connection/steam/server.rs#L105
Yes, those entities are owned by the local client, so the local client disconnects we should just despawn them
is there some reason ReplicateOnceComponent would cause the component to replicate zero times? i see there's tests around it so i gotta assume it's user error.
// on server
commands.spawn((
TestComponent(5),
ReplicateOnceComponent::<TestComponent>::default(),
ServerReplicate {
group: REPLICATION_GROUP_STATIC,
..default()
},
));
app.register_component::<TestComponent>(ChannelDirection::ServerToClient);
above results in TestComponent not on my client entities at all. removing ReplicateOnceComponent makes it replicate fine. 
hm yea i'm not sure since the test server::replication::send::tests::test_component_update_replicate_once test seems to run fine
it is looking like it only works for entities spawned (or maybe components added, idk, no difference here) after the client connected. intended?
Ah, that's possible. You spawn the entity, the client connects afterwards and the component is not replicated?
right. looking at server/replicate.rs seems like it’s not handling the new connections any differently when it comes to replicate_once, since it returns early in those cases*.
https://github.com/cBournhonesque/lightyear/pull/862
i tried to write a test but it looked like your test app bootstrap sets up and connects everything all at once, so couldn't suss out an easy way to test this
Thanks I will check it later!
The replication logic is very intricate
It's easy to miss stuff
Should the MessageRegistry resource be inserted into the app implicitly by Lightyear somewhere? I'm trying to send a message after registering a message via app.register_message and am getting:
Requested resource lightyear::protocol::message::MessageRegistry does not exist in the `World`.
Did you forget to add it using `app.insert_resource` / `app.init_resource`?
Resources are also implicitly added via `app.add_event`,
and can be added by plugins.
Oh i think i know what happened, it might be due to the message refactor
Out of curiosity, could you share your code (it can be via private DM)
i think i have a fix but i'd like to know what can trigger this
It is added into the app, but there are some commands that temporarily remove the MessageRegistry via resource_scope
Absolutely one second
I just sent you a collaborator invite via Github if that's OK, should be able to see everything in a browser
If you prefer I DM everything instead lmk
Basically network configuration stuff is under:
./client/src/plugins/network
./server/src/plugins/network
And the message I'm trying to register is under
./client/src/plugins/lobby/protocol.rs
./client/src/plugins/lobby/plugin.rs
./client/src/plugins/lobby/systems.rs
Is it possible to get the id of the client that sent an event from send_event / trigger_event , or is messages still the only way to achieve this?
Message is the only way to achieve this, since for send_event I directly send the event in Events<E>. But if you have any idea for how the id could still be available i'm all ears
I guess creating some wrapper event called FromClient<E> ?
@tropic jackal the ClientPlugins and ServerPlugins must be registered before you register any shared plugin (for example the protocol plugin)
so this line is causing an issue:
.add_plugins((CameraPlugin, CommonPlugin, LobbyPlugin, NetworkPlugin))
since NetworkPlugin must run before LobbyPlugin
Also I don't think your code is going to work, because the ProtocolPlugin that defines the Messages has to be shared between the client and the server
I would suggest putting the protocol in a shared folder: https://github.com/cBournhonesque/lightyear/blob/main/examples/simple_setup/src/shared.rs
Ahhh very interesting. Thank you for taking a look - yes I don't have any server-side Lobby stuff. Just wanted to see if I could "send" a message without anything blowing up (even if it means nothing will happen).
Thanks again!
Yeah FromClient<E> makes sense to me. It's pretty much what MessageEvent is doing, but with the added trigger_event convenience.
Yes in my head there was a dichotomy between:
- wrapper event with more context (client_id):
MessageEvent - convenience methods to directly network the Event itself without any wrappers
But maybe there's room for also triggering MessageEvent automatically
A trigger on MessageEvent would definitley go a long way for my purposes.
I think I'm misunderstanding the utility of event replication without context though. It essentially makes for a "client authoritative" event since there's no way to verify the client that sent it.
Server->Client makes sense though.
Now that i'm thinking about it, yes it doesn't make too much sense.
Some users were asking for some convenience function to seamlessly replicate and send the event E directly. I assume it's because their code was internally reacting on event E via EventReader<E> and they didn't want to write a 'wrapper' message that reads EventReader<MesssageEvent<E>> and then writes the event to EventWriter<MessageEvent<E>>
but maybe they should switch to using EventReader<MessageEvent<E>> if they are using networked events
Yeah, I'm in that issue
https://github.com/cBournhonesque/lightyear/issues/712
But yeah I do think Server->Client seamless events without context is totally fine. That is the way that Bevy Replicon handles it, but with a ToClients wrapper for an EventWriter instead of using a connection manager.
I kind of still want to keep EventReader<MessageEvent<E>> in both directions.
I do support rebroadcasting messages so if client1 sends a message to client2, I would like the MessageEvent on client2 to have message.from = Client1
Oh, nice. I hadn't seen the _to_target variants of the various send functions yet.
Hm, the ToClients wrapper for EventWriter is to increase parallelism I'm guessing?
So there's a single system that needs Mut<ConnectionManager>, reads from all the ToClients events and buffers them in the transport; instead of all the systems running sequentially because they use Mut<ConnectionManager?
i think that's a good idea, i'll think about it
For now my plan is probably to get rid of the send_event() as I'm not sure it's useful.
And then to change trigger_event to trigger a MessageEvent instead of Event
I may not have been clear enough, it's EventWriter<ToClients<MyEvent>>
But I think your assessment still holds.
And then it's EventReader<FromClient<MyEvent>>
yes that's what i meant
The EntitySpawn event appears to fire off before any of the initially replicated components are added to the entity. This does limit some use cases with ClientReplication. Should the event wait until after, or maybe some other InitialEntityReplicated kind of event should fire after the initial round of replication has been completed?
Oh, nvm. Found a bunch of workarounds.
In general I wouldn't rely too much on those events, I'm not sure that they are very useful compared to something like Trigger<OnAdd, Replicated>.
I was actually thinking of removing the events altogether
Yeah, that makes sense. Do you have an example of applying a simple version of the lerp function for impl Linear? I'm doing a bunch of reading on lerp in general but finding few examples I can use for bevy
The component im lerping contains one field, a Vec2, so not too difficult I would think
It's interesting in replicon a given Message/Event is tied to a channel; while in lightyear the two are decoupled, you can send any Message (data) on any Channel (reliability/ordering config)
This does mean that the replicon api is simpler to use (ToClients just contains the message data + destination).
If I were to build a similar API, I would have to think if I want to provide parallelism on the Messages (ToClients<M>) or Channels (ToClients<C>)
it's true that in most cases users always send a given Message type on the same channel
In the case the event is always on a channel would it be something like the following?
app.register_event::<MyChannel, MyEvent>(ChannelDirection::...);
This
connection_manager.send_event(ToClients::<MyChannel, _>(MyEvent));
Is a pretty decent API IMO
Oh wait, it would be an EventWriter
my_writer.send(ToClients::<MyChannel, _>(MyEvent))
Hmm, actually Tuple struct wouldn't work there.
I have idea for how to make it work, but i'm wondering if it's necessary. Most usecases would only want to send a message on a single channel, no?
I think you're right. Different events for different channels makes sense.
I abandoned client authoritative camera in favor of action_state.set_axis_triple() by the way. In hindsight it is the better system. You probably could have kept Entity::PLACEHOLDER for failed mappings after all 😅
I currently have a Predicted local player with a Tnua controller and other players are Interpolated. I only get corrections on player collisions which makes sense and is acceptable for me.
Aha well it might still be useful to have a Predicted component referring to a Confirmed entity; we'll see!
Which, incidently, it currently isn't possible to Predict the host properly in HostServer mode since host inputs aren't sent to clients.
you mean that other players cannot predict the host properly?
can't the host send inputs to other clients?
You can replicate an input from client->server->client, but the client that is the host in HostServer mode does not send its inputs to connected clients.
You don't even get a host predicted ActionState or InputBuffer on clients.
(in v0.19, idk about main)
Do you have a code example? theoretically lightyear should provide all the tools to make this work
@empty rampart might have a more readily available example. I obliterated mine in favor of interpolated remote clients. If they don't have one or aren't available I can put one together later.
But it was with a replicate_inputs like in the examples and ActionState on the server copy of the entities.
I don't have an example available but I mostly followed the avian_3d_character example
yea i noticed that in host-server mode other clients seem to not be able to predict the host client
@unkempt sedge also had issues predicting the host? Maybe they have an example ready?
you can clone our mre_scene, sample and just refactor it to use host server mode
but no we dont use host servers
As as Mr Shatur once said, the worst thing that can occur i n a game is a server crash. And leaving that to a client is beggin to make it occur
Having some trouble with change detection on replicated components. I've tried Trigger<OnReplace, MyCoolReplicatedComponent>, Trigger<OnInsert, MyCoolReplicatedComponent>, and Trigger<ComponentUpdateEvent<MyCoolReplicatedComponent>>, but it's not firing.
I've confirmed with debug on lightyear that MyCoolReplicatedComponent is being written across the network at appropriate times
Gone through all of the Server and Client variants, just noticed those. Confirmed that changes are indeed happening to MyCoolReplicatedComponent
I'm missing something obvious here, I feel it
Implemented Diffable, Add, and Mul<f32> just in case... still can't get any hits
Put in a constantly checking system in Update instead. It's weird I couldn't get the observer to trigger, though
happy birthday lightyear 🍰 2 years since first commit on github 🙂
i'm about 6 yaks deep at the moment doing other stuff, but will be back on the networking stuff before too long.
It's because OnReplace triggers only when a new component is inserted, whereas I just mutate the existing component value
The OnReplace trigger is a bit dangerous for that reason
Mr Peri solve an argument for me do you prefer your struct above the plugin impl or below?
what struct
Does rollback only rerun FixedUpdate, or all of FixedMain?
It runs FixedMain
If the server has an UNSPECIFIED address, how does anything find it? Both mac and OS return address not valid errors, error code 10049 on windows and 65 on mac. I've checked for firewalls and weird features but I can't figure out why anything that isn't LOCALHOST won't connect.
ERROR lightyear::connection::netcode::client::connection: error updating netcode client: Transport(Io(Os { code: 10049, kind: AddrNotAvailable, message: "The requested address is not valid in its context." }))
Only non errors are on Mac, when both server and client are UNSPECIFIED, but it's just
client connecting to server 0.0.0.0:[PORT] [1/1]
client connect failed. connection request timed out
pinging 0.0.0.0 results in an unresolved host. What's the idiomatic way UDP sets up remote connections? Is there a hostname thing that's used?
Not entirely sure; server should use 0.0.0.0:port, and client would then connect to it using 127.0.0.1:port
If you're using a remote server, then the firewall/etc. is usually handled for you; so you just set 0.0.0.0:port on the server, and the client uses SERVER_IP:port
I get the feeling there's some special situation with my setup I haven't found yet.
Is there a possibility for a RollbackSchedule instead?
I don't think it would be possible otherwise for a system to run fixed, but not as part of rollback.
You can add the system with run_if(not(is_rollback)) to exclude a system from rollback
you have two computers, one windows, one mac, connected via a local network? one is client, one is server?
Yes. Same binary app that can switch between host server , server, client modes. Tested every combo
okay. so the server binding to 0.0.0.0 and whatever unconflicted port should be fine.
the client needs to connect to the IP assigned to the server’s device on your local network by your router. are you using that / confident it’s correct?
I’ve only been using Ipv4Addr, and I’ve punched in both the private 192.168.0.* addresses as well as the public addresses and even the default gateway. Got to the point where I was throwing whatever at it to see if any hints would come out
never even bother with public, looping back around to your own router like that is a special kind of hell sometimes
let me go ahead and give you confidence that the 192 addresses are correct. you can ping between them?
Pings between machines, with the 192’s work as expected. MacOS was being difficult, I needed to give the terminal permission to allow LAN connections
I’m not home right now to test anything, and won’t be for a few hours
mmk, i can actually test lightyear local windows <- -> mac for you too sometime today too. def could be a funny os setting somewhere, or some firewall thing, or etc. i'll see if i can get my project to connect
k when you're back what works for me is
client AND server bind to 0.0.0.0. client can use port 0 (random port), but server binds to specific port.
then just connect the client to server_lan_ip:server_port. (edit: instead of UNSPECIFIED you should also be able to bind to the local device's LAN IP)
i did have to accept lan connections for the terminal session on the mac for it to work. firewall on mac needs to explicitly allow the connection if you have it turned on, but it should ask you
in the process i found some bug where if the client disconnects during the handshake the server will never stop trying to send them packets 😭
@pine cape issue + failing test for the handshake thing https://github.com/cBournhonesque/lightyear/issues/875
thanks!
@pine cape Mr Peri how would you implement a sword collider in multyplayer game? It is transform is set by clikent animations, so I am kinda in a pickle
I think this is a case for client rpelication no?
As sword positioning is based solely on the colliders position and rotation
i'm not sure, i would try to do more reading
If the sword is predicted you might have to do lag compensation when interacting with other objects/players
The sword itself is not necessarily predicted, is more like animation bone defined
yeah but that is happening on the predicted entity, I presume
You could also try to replicate each bone, etc. from the server
but you might be right indeed i need to think this through
@long marsh in your example, weirdly the client never calls the on_disconnect system which should trigger whenever the state changes to NetworkingState::Disconnecting. I'm not sure why
the disconnect could be degenerate anyway; what if the client alt+f4'd instead of calling commands.disconnect_client()
then the server would time out the client after a while
(but in your test the client keeps sending pings which prevents timeout)
is there a way for me to murder the client in a test if we wanted to prove that? that alt+f4'd clients mid-handshake time out correctly?
curious about the actual test i gave you though...
yeah i'm really surprised that this happens. I was thinking that it was because somehow the client was already in the Disconnecting state. (OnEnter doesn't trigger if you re-enter from the same state, but it doesn't seem to be the case)
you could call something like stepper.server_step() which only updates the server, but not the client
Success! Only seems to work for me if client is UNSPECIFIED:0 or 0.0.0.0:0. I also needed to restart my network and get new private IPs before it would work.
I keep forgetting to try that old IT standby first!
@pine cape I figured out a very decent solution Mr Peri, I made it so my collider is a bullet and it follows a pre-estabilished path. Of transforms. So now I have high precision + common pre predicted + server spawn logic.
Great, so you don't need to replicate the bullet after it first fires
That's also what i do in my fps example
well damm i should have seen that
actually it's here: https://github.com/cBournhonesque/sixdof
Multiplayer fps game made in bevy using Quake maps - cBournhonesque/sixdof
I don't understand what you mean
Yes I see how my little stroke of a phrasing sounds confusing hohoho
What I meant to say was, is it common for a pre predicted bullet to have rollbacks on spawn? I guess it i s when server spawns the bullet correct?
There should be no rollbacks on spawn for pre-predicted entities; if you have some it's a bug
I have changed a bunch of things recently so it's possible that this bug has been re-introduced
hmm might be just ought curiosity in the spaceship sample we reintroduce a physical body to bullet https://github.com/cBournhonesque/lightyear/blob/f677ed9d6bfca9bd2c6485e757a00831319d464d/examples/spaceships/src/client.rs#L57
Here any ideas why? I believe the pre predicted entity already contains the physical elements no?
yeah i notice, btw loved the non necessity for mutable references in messages
Idk, the exampel seems buggy; how come we don't fire the bullet here if identity.is_client()?
Exactly, i got confused too guess it is just a forgot perhaps
oh no with we turn it off there is no collider, wut
oh the system doesnt run when it has rollbacks
which is basically always when we have two entities
due to collisions
@pine cape I believe we are having rollbacks with pre predicted spawn, if you would like I have a somewhat of a mre that I can show you. That displays the bug. Problem is it is a private repo
I can probably reproduce it with the examples already; i'm busy with some other stuff but i can take a look later
btw it is system ordering based it seens
any guesses why my correction_fn for Transform is making my player jitter?
// this jitters
.add_correction_fn(|start, end, t| {
lerp(start, end, t)
})
// this works perfect
.add_correction_fn(|start, end, t| {
end.clone()
})
the lerping correction fn seems to work fine on my projectiles. just makes the player jitter. 
@unkempt sedge I think i'm close to having a PR that will fix replicating with rooms/interest-management and parent-children hierarchy, but it will only work with bevy main
And without correction you don't have any visible rollback artifacts for the player?
It might be some interplay between correction and input_delay
Hmm we could wait right now we are just using node like components
correct, works perfect w/ no correction. i have input delay set to 0
even when just holding a single direction
god i hope it’s not because im using LWIM main branch or something (edit: nope, not any of my forked deps
)
nope it's probably lightyear; i would try to log the player position before/after correction to see what's happening, but it's kind of tedious
oh interesting. i have a custom LWIM axis input that makes my character turn to face the mouse. disabling that input entirely (or booting the game and doing zero turn inputs) fixes the position jitter. oof
but it doesn't matter if i'm actively rotating, just if i've rotated at all since the game started. crimeny
switching to replicating Position and Rotation over Transform fixes it 👍
can i tune the duration corrections are smeared over?
Hi, I've been trying to move over to using PreSpawnedPlayerObject for some objects that really should be (previously I just had them spawn late because it was easier, but I'm trying to clean up my implementation), but I'm having a bit of an issue with double images.
As we discussed in #networking I now have some system that work the same way on client and server, and one of these is the system that adds a sprite to the "minion" that I'm trying to prespawn. So I have this filter that checks for Predicted, Interpolated, and Replicating, and that works great for the most part, but when using PreSpawnedPlayerObject the visuals don't appear until the Predicted entity is created, so I tried adding PreSpawnedPlayerObject to the filter, and that works as expected, it creates visuals immediately, which is exactly what I want, but then the moment the replication comes through it creates a double image, and then after a little while the PreSpawnedPlayerObject seems to be despawned, so it seems like maybe the prespawning isn't quite able to match to the right entity maybe?
Ah, I think I figured it (and another issue I happened to have with Replicating overwriting my work, presumably because of mis-predictions). The change was as simple as changing Time to Time<Fixed>, however this is kiind of bad, because that's supposed to be happening automatically. Maybe something to do with the way the FixedUpdate system is ran causes Time sometimes not to be Time<Fixed>? Regardless it makes perfect sense that relying on real Time would cause mispredictions.
Looking in the egui Inspector I still see the PreSpawnedPlayerObject disappearing, but maybe that's expected?
that’s expected
i checked the Time thing and it looks ok in my systems using the regular one
Hm, this project is still on Bevy 14, so maybe it's a bevy version thing
Is there a way to fix the pre-spawned object from disappearing when the replication kicks in? It's only a a few hundred milliseconds depending on ping, but in that space between spawning and replication, if I select the minion then it gets unselected the moment replication kicks in and deletes that entity
oh good it's not just me!! ive been debugging that for HOURS. it's only when something triggers a rollback. kinda like this? in this video when i stand still and shoot, all good. when i move (making the projectiles mispredict some / rollback), the prespawn vanishes the entity for a misc amount of time
I know for sure that PreSpawning was not causing any rollbacks a few weeks ago, some of my recent changes must have broken it
Ah that's good to know. I've also switched to replicating Position/Rotation instead of Transform because Transform was causing a bunch of issues.
Those are pretty subtle, I should document them somewhere. I suspect that it's because the VisualInterpolation system restores the Transform value in PreUpdate, and some of the avian systems rely on GlobalTransform, which hasn't been updated. Maybe running an extra transform_propagate system in PreUpdate after VisualInterpolation restore would fix the issue
You can check the settings I use here: https://github.com/cBournhonesque/sixdof which doesn't have any rollback (except on collisions, but that's an avian issue)
Ok I just read this;
- PreSpawning just works in FixedUpdate because we need to have access to correct tick information. FixedUpdate systems should all use
Time<Fixed> - The FPS example (https://github.com/cBournhonesque/lightyear/tree/main/examples/fps) does exactly what you mention with no rollback or despawning. If the PreSpawnedPlayerObject is despawned, that means that there was no match and the server-replicated entity was simply normally Predicted. This is also why you're seeing 2 images; if the matching was working you would just see one because the server-replicated entity would be merged inside the client pre-spawned entity.
If we don't find any match, we just despawn the entity after a short while. So basically I don't think the PreSpawn systems are currently working for you.
Have you tried using thevisualizerfeature to get more information about the matches? Or you could also print the PreSpawn hash on both client/server to see if it's the same
oh, indeed, my disappearing projectiles problem seems to be a problem with my rotation systems causing rollbacks still
fixed that! there we go. now i just have my projectiles causing rollbacks when i shoot them, and they are stuttery 🥲
I'll try out the vizualiser, which I hadn't heard about, and I'll check the hashes, how exactly does the hashes work? Given that the entity will likely be in a different location, it can't be based on position, right?