#lightyear
1 messages ยท Page 12 of 1
Thanks, I'll check it out
Ooops.. I just made the repo public
legend! that seems to work!
So looks like PhysicsSet::Sync -> PredictionSet::UpdateHistory is the key here IIUC
Yes, i'm actually not even sure why the PredictionSet::UpdateHistory -> PhysicsSet::Sync order works when replicating Position/Rotation.
Intuitively it would seem like you would need to first do a physics update (update Position/Rotation) and then update the history.
I must have had a reason but I cannot recall it right now
I just finished re-adding Host Client mode!
I only enabled it for the simple_box example for now; you can test it with cargo run -- host-client -c ID (and cargo run -- client -c ID to spawn another client).
Does someone experienced with bevy UI know why the 'Disconnect' button does not work in host-client mode?
The Interaction component seems to pick up the button click, but the observer doesn't get triggered for some reason
Not sure if it's related, but I think there's a system ordering issue. The example was working and then after a few recompiles it stopped working
it's possible; there's a lot of subtle edge-cases and i haven't ported all the tests over from the previous version.
I'm porting all the other examples, which will help unearth more issues: https://github.com/cBournhonesque/lightyear/pull/1029
I think the issue is race conditions around observer ordering
i'm happy we are approaching feature-parity with before the refactor
yes, it's looking really good. Good work
That or the entity has the client component removed/doesn't exist before the observer is run
Oh, it's because both the server and client nodes overlap
oh
The server ui node spawns on top and captures all of the input events
I saw an issue about this in the past where you have overlapping nodes that don't pass the input through. I'm looking for it now
I guess we also don't need a Node that takes up the whole screen
we could just position directly the buttons/text
That would be the best since they're currently not parented to the same root node.
Yep. Changing the width for both buttons to %50 works
Also had to add justify_self: JustifySelf::End, to the client node
I'll open a PR
perfect, thanks
are you sure this works? i still can't seem to click on disconnect
in host-server mode
If I add logging, I definitely get a button click that wasn't working before. The actual disconnect I think is still broken
trying to connect between to machines, but i get this error:
ERROR system{name="lightyear::client::networking::receive_packets"}: lightyear::connection::netcode::client::connection: error updating netcode client:
Transport(Io(Os { code: 10054, kind: ConnectionReset, message: "An existing connection was forcibly closed by the remote host." }))
is this a firewall issue?
It not seem to be a firewall. If you are on Linux, you can test your connection by "sudo traceroute -T -p {port} {machine_ip}" + "telnet {machine_ip} {port}".
sorry, im on windows
The error message is saying the connection was closed by the remote host (probably the server). Did you configured the TLS for the server? I think it is necessary to make the handshake in QUIC.
I didn't tried the library yet, but it sounds it is missing a configuration in the server side, then it is closing the connection.
i see, im not sure i did that, ill check it out though
@lyric badge It sounds there is a script for creating a valid certificate for 2 weeks.
The simple box example seems to be a good starting point:
https://github.com/cBournhonesque/lightyear/blob/main/examples%2Fsimple_box%2FREADME.md
This doesn't look like a certificate issue though
what do you think it could be?
it might be a firewall issue; are you using udp? where are the machines located
it's his personal machine? yeah i think you'll run into firewall issues
yes
he turned off his firewall but we still couldnt connect
you might need to do NAT traversal; it's not straightforward
you want your game to be client-server?
the easiest to test is to get a cheap box with a provider (hertzner, digital ocean, etc.)
all of them offer credits which give you 1 month free
this one is very easy to setup in particular: https://www.kamatera.com/free-trial/ that's what i used personally
yea
um in this it mentions port forwarding, is that not enough to connect to each other?
maybe; you also might not be doing it correctly
why maybe?
also we've setup minecraft/terraria servers before with port forwarding, we did it the same as that basically
I just don't know enough; i've only ever tested with cloud VPS
I figured it out. It's because the disconnect fn query for client_plugin expects a NetcodeClient from the Client entity, but when the simple_box sets up the Server and Client, it spawns a Client without the NetcodeClient. NetcodeClient is setup by the ExampleClient in common but the simple_box host-client mode doesn't use the ExampleClient
oh great catch
so the disconnect does nothing because there is no NetcodeClient
I need a specific Disconnect observer for HostClients
Exactly, it's calling the observer but doesn't find the component on the triggered entity
I do already have a special connect observers just for host-client, so this makes sense
thanks! LGTM
Just pushed more fixes: https://github.com/cBournhonesque/lightyear/pull/1029
all examples should now work with host-server mode apart from FPS!
has anyone looked into using bevy_enhanced_input with lightyear? it's probably easier for me to rewrite my game to use leafwing than try to update lightyear, but I was just curious if anyone has looked at it
enhanced input seems is more likely the future standard
so you may not want to switch to lightyear
@pine cape might be able to advise if lightyear is doing anything particularly special with it's builtin lightyear support.
ie. whether something similar can't be done third party for enhanced input
that's why I had been writing my newest game using BEI, but lightyear seems like the best fit for my needs: replication, client side prediction+interpolation+correction, and lag compensation
I've done some of that myself in the past in fyrox and it's complicated enough without all the additional system ordering issues and abstractions
unless there's an active replicon crate with similar functionality I'll probably stick with lightyear for now and switch to leafwing if necessary
It's something I want to support
do you anticipate it would require changing anything other than light_year_inputs_leafwing? not sure how deeply coupled it is with those features or anything
I know there are unique ordering issues etc
I probably need to research more about how they differ tbh
I've tried to make lightyear_inputs abstracted away from the underlying impl; it requires an ActionStateSequence (https://github.com/cBournhonesque/lightyear/blob/main/lightyear_inputs/src/input_message.rs#L39)
and I currently have 2 implementations, one for leafwing and one for native inputs https://github.com/cBournhonesque/lightyear/blob/main/lightyear_inputs_native/src/input_message.rs#L16
(the Sequence part is so because I want to send the inputs for multiple ticks for redundancy, and each implementation needs to specify how the Inputs for one tick can be computed from the previous tick, in case you can optimize the serialization using diffs)
makes sense
I think that for BEI I could use the same structure, where the sequence contains a history of the Actions (https://github.com/projectharmonia/bevy_enhanced_input/blob/master/src/action_map.rs#L31) for each Context
If I'm able to make any progress with BEI are there tests that would help me verify things?
also what state is main in currently? I know you just did a major refactor, but do the client server features seem to be working and are tests up to date?
Main is in a pretty good state, almost all examples work and I ported a lot of the tests. You can just go in lightyear_test and run cargo test
I've added some tests for input handling but those are a bit tricky
Hello!
I am using bevy 0.16.1 and lightyear 0.20.2. I run the client and the server in the same app but in a separate thread. I tried to add a lifetime to bullets, with the shooting mechanic being inspired by the "fps" example in the lightyear repo. I have this shared method system:
pub(crate) fn lifetime_despawner(
q: Query<(Entity, &Lifetime)>,
mut commands: Commands,
tick_manager: Res<TickManager>,
identity: NetworkIdentity,
) {
for (e, ttl) in q.iter() {
if (tick_manager.tick() - ttl.origin_tick) > ttl.lifetime {
if identity.is_server() {
// info!("Lifetime despawning server-side {e:?}");
commands.entity(e).despawn();
} else {
// info!("Lifetime predicted despawning client-side {e:?}");
commands.entity(e).prediction_despawn();
}
}
}
}
On the shoot_bullet shared function, I simply add my Lifetime struct:
// Inside the `shoot_bullet` shared system
// [...]
let bullet_bundle = (
Name::new("Bullet"),
Bullet,
// store the player who fired the bullet
BulletOwner(id.0),
bullet_transform,
LinearVelocity(bullet_direction * BULLET_MOVE_SPEED),
RigidBody::Kinematic,
// Lifetime component here
Lifetime {
origin_tick: current_tick, // tick_manager.tick()
#[allow(clippy::cast_possible_truncation)]
lifetime: (FIXED_TIMESTEP_HZ as i16) * 2,
},
);
Every other launch of the game, the app will crash when there is an attempt to predict despawn a bullet and I get this error:
0: bevy_ecs::system::function_system::system_commands
with name="shared::player::lifetime_despawner"
at /Users/klaus/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/system/function_system.rs:65
thread 'main' panicked at /Users/klaus/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/error/handler.rs:141:1:
Encountered an error in command `<<bevy_ecs::system::commands::EntityCommands as lightyear::client::prediction::despawn::PredictionDespawnCommandsExt>::prediction_despawn::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: The entity with ID 62v2 was despawned by /Users/klaus/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lightyear-0.20.2/src/client/prediction/despawn.rs:89:24
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic when applying buffers for system `shared::player::lifetime_despawner`!
Encountered a panic in system `bevy_app::main_schedule::FixedMain::run_fixed_main`!
Encountered a panic in system `bevy_time::fixed::run_fixed_main_schedule`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
The problem is, it doesn't happen every time. On the other hand, it's either going to crash right from first bullet thrown when it should despawn, or not at all and works for as many bullets I fire. I've tried lots of things without success. Does anyone have any clues as to what might be the problem? The fact that this doesn't happen everytime I start the game disturbs me.
It's a race condition between multiple despawns; it basically means that the despawn on https://github.com/cBournhonesque/lightyear/blob/0.20.2/lightyear/src/client/prediction/despawn.rs#L89 needs to be replaced with try_despawn because the entity has already been despawned by your lifetime system
I'll release a patch
Thanks a lot for the quick reply and help!
I've been working with Shatur on integrating BEI; it will require some changes on the BEI side.
I'll be going on a holiday weekend tomorrow, but I might have a prototype by Sunday
๐ is Lightyear in some kind of weird state right now? I'm getting panics or failures to compile on almost every example as of v0.21.0-rc.1.
Hi, yes there's been a big refactor (https://github.com/cBournhonesque/lightyear/pull/989) and some things are not still not up to part with lightyear 0.20.2
However almost all examples (apart from delta-compression and distributed-authority) should work on the main branch with cargo run -- client -c 1
cargo run -- server and cargo run -- host-client -c 0
Ahhhh got it, I'll try main out
oh this was literally a month ago that explains it
I just tried the 0.20.3 version to see if it fixed my issue with lifetime prediction despawn of a bullet, but I still have the issue sadly. (I ran cargo clean before trying again)
0: bevy_ecs::system::function_system::system_commands
with name="shared::player::lifetime_despawner"
at /Users/klaus/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/system/function_system.rs:65
thread 'main' panicked at /Users/klaus/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/error/handler.rs:141:1:
Encountered an error in command `<<bevy_ecs::system::commands::EntityCommands as lightyear::client::prediction::despawn::PredictionDespawnCommandsExt>::prediction_despawn::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: The entity with ID 62v2 was despawned by /Users/klaus/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/system/commands/mod.rs:1802:28
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic when applying buffers for system `shared::player::lifetime_despawner`!
Encountered a panic in system `bevy_app::main_schedule::FixedMain::run_fixed_main`!
Encountered a panic in system `bevy_time::fixed::run_fixed_main_schedule`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
It's probably a problem on my side but I have no idea where it's coming from
Maybe you need to do commands.get_entity(e) instead of commands.entity(e) ?
Otherwise I would suggest upgrading to the latest version
ok thanks. I will try to update, I would have had to do it at some point anyway
Is there a way to tell on the server when I am good to send mapped entities to the client? i.e. once the client knows about it and can map it successfully
does the confirmed entities really need to be separate entities instead of having a component like Confirmed::<Transform>?
right now i have many system that check for a Added<C> but that then needs to check if it is the predicted or interpolated and ignore if it is confirmed
You could overwrite the entity map directly on the server and only set the mapping when you're ready
i personally think it's easier this way, because a lot of systems (like movement) can then easily be shared between prediction/confirmed/interpolated, and the only thing to worry about is setting the filter correctly
on the server there is no predicted, confirmed, or interpolated, so it's systems wouldn't have any filters, on the client you will most likely only work on the predicted and interpolated entities, always filtering out the confirmed, and letting lightyear itself handle the confirmed entity
right?
other topic, is there a message to indicate a client left a room?
nope, but there should be. From a user disconnecting, you mean?
yep, i should provide predefined filters for common situations. What gets tricky is handling filters correctly for running in host-client mode
yes, or changing from a room to another
My view of having it as a Confirmed::<C> would be exactly so that there would be no need for filters, but investigation is needed
@pine cape Ran into an interesting bug I have this filter
mut carrier: Query<(Entity, &mut WeaponCarrier), (Added<WeaponCarrier>, Without<Confirmed>)>,
Interesting enough, if I run this system on first replication it works fine. But if I run in room transition it also considers the confirmed entity.
But
mut carrier: Query<(Entity, &mut WeaponCarrier), Or<(Added<Predicted>, Added<Interpolated>)>>,
This one doesnt working as expected
Do you have a minimum reproducing example
I am on version 0.20.2 I intend to only open lightyear related bugs once we do the big migration. There were so many changes that I think it might not be worth it
Is this the right way to set up a custom relationship (0.20.2)? It works only sometimes, maybe some race condition. (I've spent way too long getting it this far!)
When it doesn't work, the entity loses it's ChildOf component and get these errors:
2025-06-21T00:38:13.666154Z ERROR lightyear::shared::replication::entity_map: Failed to map entity 394v1#4294967690
2025-06-21T00:38:13.669482Z WARN bevy_ecs::relationship: lightyear-0.20.2/src/shared/replication/hierarchy.rs:173:45: The bevy_ecs::hierarchy::ChildOf(PLACEHOLDER) relationship on entity 493v14#60129542637 relates to an entity that does not exist. The invalid bevy_ecs::hierarchy::ChildOf relationship has been removed.
app.add_plugins(RelationshipSendPlugin::<CurrentlyControlledPawn>::default());
app.add_plugins(RelationshipReceivePlugin::<
ServerConnectionManager,
CurrentlyControlledPawn,
>::default());
app.add_plugins(RelationshipReceivePlugin::<
ClientConnectionManager,
CurrentlyControlledPawn,
>::default());
app.register_type::<RelationshipSync<CurrentlyControlledPawn>>();
app.register_component::<RelationshipSync<CurrentlyControlledPawn>>(
ChannelDirection::ServerToClient,
)
.add_prediction(ComponentSyncMode::Full)
.add_map_entities();
when spawning on the server:
commands
.spawn((
Name::new("Crew"),
replicate.clone(),
RelationshipSync::<CurrentlyControlledPawn>::from(Some(network_player_entity)),
CurrentlyControlledPawn(network_player_entity),
RelationshipSync::<ChildOf>::from(Some(*test_ship)),
));
also happy to wait for 0.21 if there are related fixes/changes
Are both your entities in the same ReplicationGroup?
To guarantee that they will be replicated in the same message?
I might add a system to guarantee this
Otherwise one entity could be replicated before the order and the relationship insertion would fail
That's almost certainly the reason
many thanks, that seems to work! such a simple solution
Why ClientConnectionManager does not have the capability of sending message to room?
Because the client is not aware of rooms, only the server is
Perhaps it would be a nice idea to replicate the resource, on another unrelated point the entity mapper only maps confirmed to server entities correct? Do you think it is a good idea to extend it to predicted,interpolated?
No it also maps from confirmed to predicted and interpolated
But for example, if I try to map a predicted entity, to server entity. I cant do that succesfully correct? Or am I fucking up something in map_entities
Hm yeah you would have to manually map it I guess
Hmm interesting how did you manage to do the InputMessage logic them? How does input message know which entity in server it should reach?
Sorry I dont really understand, im wondering if there is a indicator when the entitymap on the client side knows of entity X that exists on the server, right now I am trying to spawn and send a message for some entity on the server, on the same tick
hello! I am trying to migrate to lightyear 0.21.0-rc1 (so many things changed! :o), how can we replicate resources on this new version? Couldn't find anything in the docs
@slow kernel I have completed the BEI integration! Here's the example: https://github.com/cBournhonesque/lightyear/tree/main/examples/bevy_enhanced_inputs
(and the code is here)
For now it requires a custom branch of BEI, we're still in discussion about making a new BEI release.
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
Hi, unfortunately it's not possible to replicate resources in the new version.
The reason is that previously resources were replicated as messages, which came with its set of limitations.
In the next bevy version there are plans to make resources components, so they would be networked as components. For that reason i'm currently holding off on porting the implementation of resource-replication since it will become obsolete as soon as bevy 0.17 is released
The way things work is that the receiver maintains an EntityMap from remote_id to local_id. (i.e. from server entity to the client Confirmed entity): https://github.com/cBournhonesque/lightyear/blob/775248fccd42c900fef5f4576a53a243f7d3e1d6/lightyear_messages/src/lib.rs#L79-L79
Whenever you send any message, I apply the map_entities fn associated with the message to perform the mapping.
If you want to send a message containining references to Predicted entities, I would first apply map_entities to the message using the predicted entity_map (https://github.com/cBournhonesque/lightyear/blob/main/lightyear_prediction/src/manager.rs#L55)
You can just query for the server_entity inside the entity_map to see if the mapping exists
I'll probably start implementing it in my game over the next week!
I see, it makes sense! I will work around it by turning shared resources into entities for now then ๐
So in summary get predicted map them send message got it
@pine cape Is it possible to observe lightyear sent custom message/events ? Between sever client
Yes, you would need to register with add_trigger instead of add_message
Does anyone know whats causing this error? This is running in the FixedUpdate schedule.
pub fn explode_shell_lifetime(
mut commands: Commands,
query: Query<
(
Entity,
&DespawnAfter,
&Position,
&Velocity,
&ChildOf,
&Explosive,
&ShellMarker,
),
Or<(With<Predicted>, With<PreSpawned>, With<Replicate>)>,
>,
timeline: Single<(&LocalTimeline, Has<Server>), Without<ClientOf>>,
) {
let (timeline, is_server) = timeline.into_inner();
for (entity, despawn, position, velocity, child_of, explosive, shell_marker) in query.iter() {
if (timeline.tick() - despawn.origin_tick) > despawn.lifetime {
let salt = shell_marker.id + timeline.tick().0 as u64;
info!("explosion salt: {}", shell_marker.id);
let explosion_entity = commands
.spawn(ExplosionBundle {
explosion_marker: ExplosionMarker,
explosive: explosive.clone(),
position: position.clone(),
velocity: velocity.clone(),
child_of: child_of.clone(),
child_of_sync: ChildOfSync::from(Some(child_of.0)),
prespawned: PreSpawned::default_with_salt(salt),
})
.id();
if is_server {
commands.entity(explosion_entity).insert((
Replicate::to_clients(NetworkTarget::All),
PredictionTarget::to_clients(NetworkTarget::All),
PreSpawned::default_with_salt(salt),
));
commands.entity(entity).despawn();
} else {
commands.entity(entity).prediction_despawn();
}
}
}
}
that is a big message friend perhaps you should do the discord code thing
Hm looks like PreSpawned is inserted several times in the network message from the server to the client, looks like a bug from my side
This is on which version?
I have a component (ShipGrid) that represents the voxels on some moving entity, each entity has a hashmap of Chunks, each Chunk is some fixed size (16x16x16) vec of voxels. Players can edit individual voxels. I want to add delta compression, since each instance of ShipGrid could contains tens of thousands of voxels.
Everything is controlled by the server so tracking changes on the server is trivial, I just write whatever changes I make to the voxels into a Vec called delta. Right now I am manually sending this delta Vec to clients whenever any edits are made however id prefer to use the built-in features that lightyear provides where possible. Is this a reasonable use-case for the type of delta compression that lightyear can do?
Not super familiar with the library so kind of stuck on the implementation of Diffable for a ShipGrid, for fn diff I would rather return the changes accumulated in the delta Vec instead of comparing two ShipGrids to each other, is this a problem? Additionally, Diffable requires the struct to also implement PartialEq, is this only used to see if a delta needs to be sent? In that case I could implement it in such a way to always return true when the delta Vec is non-empty, or would this cause problems?
Any other tips are also appreciated.
#[derive(Component, Debug, Clone, Reflect, Serialize, Deserialize)]
pub struct ShipGrid {
pub chunks: HashMap<ChunkIndex, Chunk>,
pub delta: Vec<ShipGridDelta>,
pub dirty: bool,
}
#[derive(Debug, Clone, Reflect, Serialize, Deserialize)]
pub struct Chunk {
pub blocks: Vec<Option<BlockData>>,
pub dirty: bool,
}
#[derive(Debug, Clone, Reflect, Serialize, Deserialize)]
pub enum ShipGridDelta {
AddBlock {
pos: BlockIndex,
block_data: BlockData,
},
RemoveBlock {
pos: BlockIndex,
}
}
Hi, yes I believe that would be a good use of DeltaCompression. To give you an example, I used it for my game cycles-io for bevy jam 5.
In that game players would leave a trail behind them (Vec<Point>), and I didn't want to replicate the entire trail at every new point, so instead I used delta-compression to only send the few points that were added since the last synced trail: https://github.com/cBournhonesque/jam5/blob/main/shared/src/player/trail.rs#L66
I hadn't expected that you could provide the diff without looking at the previous value, but I agree that it would be convenient for performance. I'm not convinced that it is correct though. The way delta-compression currently works is:
- on the sender (server), i keep track of which ticks were fully ack-ed by the client. Then I complete a diff from the last acked tick (for example tick 5) and i send
DiffFromTick5ForCurrentTick8 - on the receiver (client), i keep a history of the component values since the last one that was acked by the server. For example I am on tick 12 and i have tick 5 in the history. When I receive
DiffFromTick5ForCurrentTick8, i apply the diff to the stored component from tick 5 to get the value for tick 8.
I think that if you just use your delta Vec, there might be cases where things will get desynced; For example the DiffFromTick5ForCurrentTick8 message gets lost, so you now need to send DiffFromTick5ForCurrentTick12. Maybe there is a better way of doing this? I'm not sure.
And the PartialEq bound is for change-detection on the receiver; it's not super important, I can remove it.
Also please note that I haven't implemented/tested delta-compression in the main branch (after the refactor)
In your case it might be simpler to send the delta at regular intervals using a reliable channel, and not network the component at all
I see, if we are comparing the current component to different past versions of it then my idea of implementing diffable would not work. I suppose one could keep track of the changes made each tick, and delete all older ones after all clients have acked some tick. Essentially some sort of sliding window, then if we know the ticks of the two versions we are comparing we could send the right deltas from the history. Could be useful in cases where we absolutely do not want to compute a diff if its too costly performance-wise.
But I will just stick to events on a reliable channel for this specific component for now, thanks for the detailed response :)
I think a solution would be if the Diffable trait provided the user an opportunity to include the deltas + the tick information for the last few ticks.
Then upon receiving your delta I could ignore the ticks that are more recent than the ones that have been hacked.
So basically always broadcast the last few deltas (or whatever the user provides in the impl) and let the client-side filter out what has already been applied?
Yes
Assuming we send the same deltas to all clients (ie not keeping track of what each client has already received), it should already be possible to implement that just through the Diffable trait (as long as we keep track of the tick at which we record our deltas at and do the filtering in the applyDiff impl) no? Either way it would not fit my usecase. I do hope I dont come across as demanding any work from you, I do appreciate the library you've written.
@pine cape Lets say I have an entity with two distinct leafwing action states inserted unto it, would that cause conflict on input buffering? Specifically if both input based system are in fixed_update. Note - I know this sounds weird but this is the only explanation i have for this bug
@pine cape If you would like I have an easy sample for you to test out this scenario
Yes I think the Diffable trait would need to have extra tick information.
Why would it not fit your usecase? I think it would be well suited for your case. Is it because of the redundancy of sending the diffs for the last few ticks?
And don't worry about asking for more work, we're just brainstorming designs ๐
Nope things should work correctly with 2 leafwing inputs on the same entity! Yes I'd be interested in a test
Yes, I feel like its a waste resending potentially large diffs when its not always necessary. And for my usecase I dont mind if recover from desync takes a few ticks longer, for example by having the client request a resync instead of the server constantly sending redundant information.
Ah i fixed it is okay now
@pine cape are you keeping track of the changes i'm making for the PR to remove bevy proper from the crates?
I saw that, thank you!
Damn, is it worth it? Lol
haven't tested
missing the root lightyear and lightyear_tests
so that i can then start on the examples and demos
will attach timins to the PR when it is done
from before and after the PR
I don't think the examples and demos should the change
They should resemble code that users would use, and users would use the main bevy_crate
ah, yes, my mistake
no, actually, you will still have the subcrates compiling in parallel before bevy_pbr, so when it gets to lightyear all subcrates should be done
Lightyear_tests doesn't need to change either
ah, you don't mean the compile time, you mean the Cargo.toml
there are 2 crates that the tests are super broken, right?
Yea, probably, I didn't fix all tests in individual crates
lightyear_prediction and lightyear_frame_interpolation try importing types that appear nowhere
@pine cape are you available right now? can you run cargo test -p lightyear_netcode --all-features and see if any tests fail on main?
so that i can put #[ignore] for now
the tests work but some doc tests fail
even these 2?
Doc-tests lightyear_netcode
running 12 tests
test lightyear_netcode/src/client.rs - client::Client<Ctx>::with_config (line 266) ... FAILED
test lightyear_netcode/src/lib.rs - (line 47) ... FAILED
test lightyear_netcode/src/lib.rs - (line 83) ... FAILED
yeah, it is doctests on lightyear_netcode
@pine cape commented on the PR with 2 timings, one on main and one on my branch
@pine cape it is ready
Is there a convenient way to go from PeerId to the Client entity in the refactor? I'm trying to add a client to a room later after they have acknowledged with a trigger, but the RemoteTrigger gives a PeerId and RoomEvent::AddSender is expecting a client entity.
I use a component with the PeerID as a field which is added directly to the player entity the client controls
#[derive(Component, Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct CharacterMarker(pub PeerId);
I also use a PeerID - Entity map locally which is updated on connection and disconnection
#[derive(Resource, Clone, Serialize, Deserialize)]
pub struct CharacterMap {
pub characters: HashMap<PeerId, Entity>,
}
Yeah I figured I would have to go this route, but was wondering if it already existed in lightyear somewhere.
Yes there is a resource called PeerMetadata
I merged a bunch of changes that should completely fix your PreSpawned issues
Does avian still have rollback issues?
Yes, because the server uses some Collisions resource that is not replicated or rolled back.
I think the rollbacks can be avoided by adding some tolerance on the rollback checks (for example only rolling back if the delta is above 0.1)
I noticed in recent main branch, the avian_physics example disables avian's sync plugin
if this is handled by lightyear_avian, is that only for replicated entities or will lightyear_avian also reproduce the normal sync behavior for non-replicated entities
It will also do the sync for all entities
The reason is to apply the sync for entities that don't have RigidBody, such as interpolated entities
It looks in the code like it disables transform->position sync
is that the case?
OH, that's already in lightyear_avian in the version I'm working on
that guess that explains why replicating transform isn't working.
is there a problem with syncing transform?
Gotta say this is the first time i see the ci actually working, this must have been quite the improvement
also oh my hawd
I think some of the system ordering is trickier to get right with Transforms, I forgot what was the exact reason but all my tests have been with replicating Position and Transform
Is it normal behavior for confirmed entities to have the PreSpawned component?
pub fn print_entities_with_prespawned(
q: Query<Entity, (With<PreSpawned>, With<Confirmed>)>
) {
for entity in q.iter() {
info!("Entity: {:?} has both PreSpawned and Confirmed", entity)
}
}
Yes, those were the PreSpawned entities on the client that got matched with a server entity
I'm running into an odd issue, I'm pointed to the head of main (I know unstable, but wanted to jump the gun on 0.21 refactor; and what I am trying to do seems to be working in the avian_3d_character example).
I have mostly mimic'd the aforementioned example for all the netcode, but when I spawn my character on the server and it gets replicated to the client the Predicted entity is not getting the CharacterMarker (named Tag in my code) component cloned, so I cannot do any client side setup of the entity. The Tag does exist on the Confirmed entity
fn server_handle_connected(
trigger: Trigger<OnAdd, Connected>,
query: Query<&RemoteId, With<ClientOf>>,
mut commands: Commands,
) {
let Ok(client_id) = query.get(trigger.target()) else {
return;
};
info!("Client connected with id: {client_id:?}");
let entity = commands
.spawn((
Name::new("Character"),
Tag::new(*client_id),
Facing::default(),
Actions::default_input_map(),
Transform::from_xyz(5.0, 5.0, 0.0),
Hittable::new(100.0),
// children![HurtBox, Collider::capsule(0.25, 1.6)],
//Networking
Replicate::to_clients(NetworkTarget::All),
PredictionTarget::to_clients(NetworkTarget::All),
// PredictionTarget::to_clients(NetworkTarget::Single(client_id.0)),
// InterpolationTarget::to_clients(NetworkTarget::AllExceptSingle(client_id.0)),
ControlledBy {
owner: trigger.target(),
lifetime: Default::default(),
},
))
.id();
error!("Created entity {entity:?} for client {client_id:?}");
// load_character_asset(entity, &mut commands, false);
}
Any ideas what could cause this to happen? I made sure to enable replication,interpolation,prediction feature flags.
My GameClient also has
ReplicationReceiver::default(),
PredictionManager::default(),
InterpolationManager::default()
Did you register a PredictionMode on Tag?
Are you using Tag in an observer? Can I see the code for that observer?
Yes, Tag has Once for prediction and Interpolation.
app.register_component::<character::Tag>()
.add_prediction(PredictionMode::Once)
.add_interpolation(InterpolationMode::Once);
I don't believe we are using Tag in any observers, but we have a system in update to try and add the client side components
fn client_handle_new_character(
mut commands: Commands,
character_query: Query<(Entity, Has<Controlled>), (Added<Predicted>, With<Tag>)>,
) {
for (entity, is_controlled) in &character_query {
load_character_asset(entity, &mut commands, is_controlled);
error!("NEW TOON");
if is_controlled {
error!("CAM MAN ADDED");
commands.entity(entity).insert((
Actions::default_input_map(),
children![GameCamera, Transform::from_xyz(0.0, 0.35, 0.0)],
));
}
}
}
This is the system I can never get to run since Tag never appears on the Predicted object
I'm not sure; since syncing components from confirmed to predicted seems to work in the examples; maybe you could try enabling this log? https://github.com/cBournhonesque/lightyear/blob/5dc3dc3e17a8b821c35162b904b73eea0e1c69be/lightyear_prediction/src/predicted_history.rs#L171
I guess i'm missing some logs to debug this; I would have to check what the components in https://github.com/cBournhonesque/lightyear/blob/4d5e69072485faa3975543792a8e11be7608a0ea/lightyear_prediction/src/predicted_history.rs#L252-L252 are
do you think you could privately share your repo with me? otherwise i can guide you on what logs to add
Up to you, i can fork and inject comments too. (whichever is easier)
Can you try replcaing that part of the code with
let components: Vec<ComponentId> = entity_ref
.archetype()
.components()
.filter(|id| {
let ok = prediction_registry
.get_prediction_mode(*id, &component_registry)
.is_ok_and(|mode| mode != PredictionMode::None);
let kind = component_registry.component_id_to_kind.get(id).unwrap();
let name = component_registry.serialize_fns_map.get(kind).unwrap().type_name;
info!(
"Checking if we should sync component {name:?} from confirmed {confirmed:?} to predicted {predicted:?}: should_sync = {ok}",
);
ok
})
.collect();
One of those unwraps() is panicing
Let me find out which
hm i guess it's expected; because some components are not registered
maybe just do a if let Some() for the first check
Ok, I'll fix
Tag is set to false !!
Also I have another type that is mine also set to false.
and both are registered
so the PredictionMode is somehow None?
In which order do you register your plugins
Protocol is before Server or Client.
or are you sure your ComponentRegistry is not reset somewhere
Only place it could maybe be changed would be with Skein, other than that my code itself doesn't touch that. I also wouldn't expect Skein to clear that, since it's the blender plugin.
fn runtime(runtime: Runtime) {
let mut app = App::new();
app.add_plugins((
DefaultPlugins.set(AssetPlugin {
watch_for_changes_override: Some(false),
..Default::default()
}),
EguiPlugin {
enable_multipass_for_primary_context: true,
},
GameCameraPlugin,
HanabiPlugin,
FpsOverlayPlugin {
config: FpsOverlayConfig {
text_config: TextFont {
font_size: 24.0,
..Default::default()
},
text_color: Color::srgb(0.0, 1.0, 0.0),
enabled: true,
refresh_interval: Duration::from_secs_f32(0.1),
},
},
PhysicsPlugins::default()
.build()
.disable::<SyncPlugin>()
.disable::<PhysicsInterpolationPlugin>()
.disable::<SleepingPlugin>(),
PhysicsDebugPlugin::default(),
SkinnedAabbPlugin,
SkeinPlugin::default(),
graybeard::abilities::AbilityPlugin,
graybeard::util::ttl::TimeToLivePlugin,
graybeard::protocol::ProtocolPlugin,
graybeard::level::LevelPlugin,
));
app.insert_resource(SleepingThreshold {
linear: -0.01,
angular: -0.01,
});
app.add_systems(
PostUpdate,
position_to_transform
.in_set(PhysicsSet::Sync)
.run_if(|config: Res<avian3d::sync::SyncConfig>| config.position_to_transform),
);
app.add_systems(PostStartup, set_window_title);
match runtime {
Runtime::Server { addr } => {
app.add_plugins(graybeard::server::ServerPlugin { addr });
}
Runtime::Client { server_addr } => {
app.add_plugins(graybeard::client::ClientPlugin { server_addr });
}
Runtime::ClientHost { addr } => {
app.add_plugins((
graybeard::server::ServerPlugin { addr },
graybeard::client::ClientPlugin {
server_addr: "127.0.0.1:63436".parse().expect("DED PARSE"),
},
));
}
};
app.run();
}
i would try to see if the ComponentRegistry is empty after your ProtocolPlugin, and then after your Server/Client plugins
That seems odd because Name is getting sync'd and it's set in protocol. At least to the Confirmed ahh but so is tag, it's just Predicted thats goofed
Let me add you to the repo maybe it's easier to poke around
ah found it
i should probably change that; that seems super error-prone: https://github.com/cBournhonesque/lightyear/blob/4d5e69072485faa3975543792a8e11be7608a0ea/lightyear_prediction/src/registry.rs#L408-L408
looks like the PredictionRegistry needs to be present before ProtocolPlugin is called
it's annoying that bevy doesn't have dependencies between plugins
Yea, totally.
yes, protocol should be added last
Ok, will give that a go.
or move everything in your protocol from Plugin::build to Plugin::finish
Maybe a panic for now if that's empty? I like fail fast
yep, will do
what panic
2025-06-29T17:51:59.730889Z TRACE lightyear_prediction::registry: Adding prediction for component "lightyear_replication::hierarchy::RelationshipSync<bevy_ecs::hierarchy::ChildOf>" with mode Once
thread 'main' panicked at /home/zach/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/world/mod.rs:356:57:
called `Result::unwrap()` on an `Err` value: ArchetypeExists(ComponentId(415))
stack backtrace:
0: __rustc::rust_begin_unwind
at /rustc/4d08223c054cf5a56d9761ca925fd46ffebe7115/library/std/src/panicking.rs:697:5
1: core::panicking::panic_fmt
at /rustc/4d08223c054cf5a56d9761ca925fd46ffebe7115/library/core/src/panicking.rs:75:14
2: core::result::unwrap_failed
at /rustc/4d08223c054cf5a56d9761ca925fd46ffebe7115/library/core/src/result.rs:1762:5
3: core::result::Result<T,E>::unwrap
at /nix/store/9kizz11q3l2qllq8lnk88zb48bz4blg8-rust-mixed/lib/rustlib/src/rust/library/core/src/result.rs:1167:23
4: bevy_ecs::world::World::register_required_components
at /home/zach/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/world/mod.rs:356:57
5: bevy_app::app::App::register_required_components
at /home/zach/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_app-0.16.1/src/app.rs:838:26
6: lightyear_messages::client::<impl lightyear_messages::registry::MessageRegistration<M>>::add_client_direction
at /home/zach/.cargo/git/checkouts/lightyear-1dda86c04cfd7c75/84efa4b/lightyear_messages/src/client.rs:15:22
7: lightyear_messages::client::<impl lightyear_messages::registry::MessageRegistration<M>>::add_client_direction
at /home/zach/.cargo/git/checkouts/lightyear-1dda86c04cfd7c75/84efa4b/lightyear_messages/src/client.rs:22:22
8: lightyear_messages::registry::MessageRegistration<M>::add_direction
Weird too the aviand3d example has protocol before client/server
nope, the plugins are added in cli.build_app
the ExampleClientPlugin is just the plugin for the example itself, not the lightyear plugin
Can i see your protocol?
Ahh yea, my client/server is both lightyear and app.
Let me add you, 1 second
Added.
Matching your ordering now.
And it works now!
So the order was lightyear -> protocol -> game client
Awesome! and before, you had lightyear -> game client -> protocol?
Yes
Well first was protocol -> lightyear -> client
which silently failed.
then the order you listed was a panic
oh i remember why it panics
it's because I call register_required_components::<Client, MessageSender<M>>
but you already had a Client entity
so you're not allowed to register
ok I really need to document this clearly
thanks
Haha, I'd be happy to help!
Me and my brother have been struggling for a while with this and previous versions.
I think, personally an example thats 1 crate might be the easiest to follow (template like) but I totally get why you have it split into common + example.
Just makes learning difficult
I'd be willing to help build something if you want.
agreed; i have this example: https://github.com/cBournhonesque/lightyear/tree/4d5e69072485faa3975543792a8e11be7608a0ea/examples/simple_setup that doesn't use the common crate
but it doesn't do anything besides the client connecting
it would be nice to have it showcase a simple replication
Gotcha, im sorta thinking the avian3d/2d wwill probably be the most common setup people would want.
But yea, I did miss that simple example, so maybe I'm the dense one.
aha no, i haven't really kept the docs up to date recently
Fair enough! I know them feels.
Neat little system to update the name of entities based on replicated/predicted/interpolated if you use egui inspect or similar with Name:
app.add_systems(Update, (
on_entity_network_type_change_name::<Replicated>("replicated"),
on_entity_network_type_change_name::<Interpolated>("interpolated"),
on_entity_network_type_change_name::<Predicted>("predicted"),
));
fn on_entity_network_type_change_name<T: Component>(
s: &str,
) -> impl FnMut(Query<&mut Name, (Changed<Name>, With<T>)>) {
move |mut query: Query<&mut Name, (Changed<Name>, With<T>)>| {
for mut name in &mut query {
if name.contains(s) {
continue;
};
*name = Name::new(format!("{} ({})", *name, s));
}
}
}
I just had a pause for 6 months, and trying to upgrade to bevy 0.16 and lightyear 0.20, and it seems EventReader<InputEvent<Inputs>> is not the way to go anymore on the server? It seems to be in a couple of readme files only
The examples are always updated with the newest versions
You gonna make boats with players on top? I have heard that is one of the most difficult things to do in multyplayer
@pine cape sorry to bother but does lightyear have a state that tells me if my client is connected
Yes, in the new version it's just With<Connected>
I would try to upgrade to the main branch, as a lot of thigns have changed. The examples are up-to-date
Physics spaceships with players in them! It is tricky for sure ๐
0.20 has NetworkingState
Hello! I'm interested in using this crate for networking via p2p using steam relay servers. Is there some example that could be a good starting point for this? ๐
Not sure how much that would differ from some of the simple examples that I found on the repo.
Hi, I haven't tested it but all examples should be compatible with steam.
You would need to create your own Steamworks client and pass it to the Steam IO
(Instead of the NetcodeClient + WebTransportIO used right now)
Alright I see! When you say "create your own Steamworks client" are you referring to "lightyear_steam/src/client.rs" or something more custom I would need to do?
You would need to create a steamworks client using the steamworks library.
I'm on my phone right now, but you should check out the docs and examples from the aeronet steam crate, which is what lightyear uses under the hood
Will do. Thanks a lot ๐
I was using .add_resource prior to 0.21, what is the new expected pattern for sharing a singleton? Local singletons + messaging? Single Entity?
Just adding it as a component on an entity (which is what resources will become in bevy 0.17, if it can get done in time)
Ahh, ok! thanks
0.21-rc.2 is working great btw!!
Can't say I ever got previous versions to work well.
The only issue I have atm is with pre spawned projectiles dissapearing, haven't began debugging, and don't really know how tbh
Other than that it's working great!
nice! you could set RUST_LOG=info,lightyear_prediction::prespawn=trace to get additional logs
What issues did you encounter in previous versions?
Constant rollback
And a bunch of weird despawn non existant errors, but upgrading seems to have gotten rid of all of it. Probably because I had to do quite a bit of refactoring with the new APIs
@pine cape what are the cases that you would want to modify the data on the Confirmed on the client side?
In the main branch I updated the simple_box example to use steam as a relay network. You have to run with the steam feature, and uncomment out the steam parts in example/common/cli
None, I think, unless you're doing something special
Btw I will be releasing the new version tonight
great, then lets do an experiment, have Confirmed as a disabling component
Hm interesting, do you need that for something? I van do that for a future release, that's a pretty big change. But I thing it would work
it decreases the amount of Or<With<Predicted>, With<Interpolated>> since Confirmed would stop appearing on queries
Hm let me think about it, that would be a win
keeping Confirmed public would still allow people to query confirmed entities if needed
Whenever sending a message_to_target, and I grab it in another client. I get the server client instead of the sender, is that expected behaviou? I am guessing that is due to server being the one actually sending the mesages
Could you please clarify? Maybe with a short code snipper/example
fn challenge_button_react(
mut query: Query<
(&Interaction, &mut BorderColor, &CarrierId),
(Changed<Interaction>, With<ChallengeButtonMarker>),
>,
challenge_ui: Query<Entity, With<ChallengeUiMarker>>,
mut connection_manager: ResMut<ClientConnectionManager>,
mut next_state: ResMut<NextState<ChallengeMenu>>,
easy_client: Res<EasyClient>,
mut commands: Commands,
) {
for (interaction, mut border_color, carrier_id) in &mut query {
match *interaction {
Interaction::Pressed => {
border_color.0 = Color::WHITE;
// The field from is needed due to server becoming the from whenever sending message
let _ = connection_manager.send_message_to_target::<CommonChannel, ChallengeSent>(
&ChallengeSent {
from: easy_client.client_id,
to: carrier_id.0,
},
NetworkTarget::Single(carrier_id.0),
);
if let Ok(challenge) = challenge_ui.single() {
commands.entity(challenge).despawn();
next_state.set(ChallengeMenu::Closed);
debug!("Sent challenge to {}", carrier_id.0);
} else {
warn!(
"He skipped the menu before we could close it ourself. Little gap abuser e.e"
)
}
}
Interaction::Hovered => {
border_color.0 = Color::BLACK;
}
Interaction::None => {
border_color.0 = Color::BLACK;
}
}
}
}``` The receiveing client cant read the from field in the given event right? Because server is the one who sent him that
I see, and the From field should be the original client who sent the message? Because the server was just rebroadcasting it?
That's amazing, thanks a lot ๐ will have a try at running it and see if I can integrate it in my own project then
yeah
Ok finally go around to getting the logs for the Prespawn entities that disappear, it looks like a hash mis-match. I am using Default for the component with it's default hashing. It's very odd to me that the hashes could mismatch, but only some times.
This is how I am spawning the entity (I truncated the let ability part, to one item, as it normally is a match statement per ability type, but this should be the static component shape both server and client see)
fn cast_ability(
commands: &mut Commands,
character: &CharacterQueryItem,
e: &crate::abilities::AbilityEvent,
charge_effect_query: &mut Query<(Entity, &AbilityChargeEffect)>,
) {
let CharacterQueryItem {
facing,
tag,
controlled_by,
position,
..
} = character;
let spawn_location =
Transform::from_translation(position.0 + (Vec3::Y * 0.35) + (facing.forward * 0.5));
client_remove_ability_charge_effect(commands, charge_effect_query, e);
/// removed large match statement to just one item for paste clarity
let ability = commands.spawn((
LinearVelocity(facing.forward * 250.0),
TimeToLive { duration: 0.5 },
Collider::sphere(0.075),
Mass(0.5),
SpawnEffect::new(e.ability_state),
children![(
Collider::sphere(0.105),
HitEffects {
effects: vec![HitEffect::Despawn],
},
HitBox {
owner: Some(e.owner),
ability_state: e.ability_state,
},
)],
))
.id();
// Insert shared components
commands
.entity(ability)
.insert((spawn_location, RigidBody::Dynamic, PreSpawned::default()));
// Check if server
if controlled_by.is_some() {
commands.entity(ability).insert((
Replicate::to_clients(NetworkTarget::All),
PredictionTarget::to_clients(NetworkTarget::Single(tag.client_id.0)),
InterpolationTarget::to_clients(NetworkTarget::AllExceptSingle(tag.client_id.0)),
*controlled_by.unwrap(),
));
} else {
// Let the server despawn the ent
commands.entity(ability).remove::<TimeToLive>();
}
}
I think one of the problems is I only want my TTL component to live on the server. How does PreSpawn generate the hash? I would assume it's quite common to want a different set of components on the server and client.
As a test I just set my own hash to the ability enum type as u64; Still get hash mis matches, which should be impossible.
I tried it, it broke a few tests; so i'll just hold off on it for now
it uses the name of the components that are present in the component registry
So it's the sum of networked components?
err, well not even
Could be more
I assume a static hash of say 0, should always work if I only spawn 1 entity shape, right?
But that seems to even fail
I won't be supporting the old version of lightyear anymore, so you will have to migrate to get fixes; although one of the missing features in the new feature is the automatic rebroadcast of client->server messages to new clients
sum of networked components present on the entity https://github.com/cBournhonesque/lightyear/blob/1aff4b06b9650c1a9b0a2def8d5a914c1007554d/lightyear_prediction/src/prespawn.rs#L429-L429
- the tick
Understandable
Gotcha, so using default the limit would be 1 entity per tick with that hash.
So given this scenario, manual hash of 0, and 1 entity or less per tick, should it always work?
I don't understand what you're saying; if you manually set the hash on server/client, then the hashes will match yes. You can enable the trace logs to confirm that
Yea, so thats what I'm trying and I still get a hash mist match
and the client despawns
well what do the trace logs say
client logs
2025-07-03T22:58:43.199937Z DEBUG lightyear_prediction::prespawn: found a client pre-spawned entity 4967v2#8589939559 corresponding to server pre-spawned entity 4985v1#4294972281! Spawning/finding a Predicted entity for it 0
2025-07-03T22:58:43.199952Z TRACE lightyear_prediction::prespawn: re-using existing entity
2025-07-03T22:58:43.199958Z DEBUG lightyear_prediction::prespawn: Added/Spawned the Predicted entity: 4967v2#8589939559 for the confirmed entity: 4985v1#4294972281 confirmed_tick=Tick(3291)
2025-07-03T22:58:45.666798Z DEBUG lightyear_prediction::prespawn: found a client pre-spawned entity 4968v3#12884906856 corresponding to server pre-spawned entity 4971v3#12884906859! Spawning/finding a Predicted entity for it 0
2025-07-03T22:58:45.666812Z TRACE lightyear_prediction::prespawn: re-using existing entity
2025-07-03T22:58:45.666816Z DEBUG lightyear_prediction::prespawn: Added/Spawned the Predicted entity: 4968v3#12884906856 for the confirmed entity: 4971v3#12884906859 confirmed_tick=Tick(3600)
2025-07-03T22:58:48.721333Z DEBUG lightyear_prediction::prespawn: Received a PreSpawned entity from the server with a hash that does not match any client entity server_hash=0
``` (took maybe 6 or so tries to get it to happen with a few seconds between each spawn
no logs from that system on server
rand with RUST_LOG=info,lightyear_prediction::prespawn=trace cargo run client
same env var for server
THere's no log "PreSpawned hook, setting the hash on the component" on the server?
No logs from the server, correct.
Only message is an internal bevy remove log for ReplicateLike
maybe useful?
does your server crate contain lightyear_prediction?
or do you compile with no default features or something
then i don't get how you don't get this: https://github.com/cBournhonesque/lightyear/blob/1aff4b06b9650c1a9b0a2def8d5a914c1007554d/lightyear_prediction/src/prespawn.rs#L386-L386
do you get those logs on the client?
ah that log doesn't trigger if you set a manual hash
I am getting these logs from the server, but nothing else
2025-07-03T23:09:17.328343Z WARN bevy_ecs::error::handler: Encountered an error in command `<bevy_ecs::system::commands::entity_command::remove<(lightyear_replication::hierarchy::ReplicateLike, lightyear_replication::hierarchy::RelationshipSync<bevy_ecs::hierarchy::ChildOf>)>::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: The entity with ID 4807v4 was despawned by /home/zach/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/relationship/mod.rs:257:21
just updated to 0.21.0, what happened to all the imports? IoConfig, ClientTransport, CompressionConfig etc.
oooh looks like theres been a lot of changes to the code, wow very nice
yep there's been a lot of changes! This https://cbournhonesque.github.io/lightyear/book/tutorial/build_client_server.html#setting-up-the-client-and-server + the examples explain how things work
โก Here is the release post! #crates message
Do you have an example of creating a crossbeam channel for separate mode server & client yet? I might have missed it but the common create doesn't seem to handle it yet
I don't do it in the examples because it made the 'common' code confusing, but you can find an example here: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_tests/src/stepper.rs#L138
ok sweet! thank you:) Also there might be a bug (or my misunderstanding) but I have a simple menu example I'm trying to update and the client will connect to the server when the client connect code is in schedule Startup, but not when I change it to OnEnter(MultiplayerState::Client)
It's possible that some transitions don't work well if you're changing between mode dynamically; when you do OnEnter(MultiplayerState::Client) do you also trigger Connect on the client entity?
so a button press changes my own personal state "MultiplayerState" from None to Client, and then the system that trigger_targets Connect is called OnEnter(MultiplayerState::Client)
Yes i am triggering Connect on the client entity, Not sure what you mean by changing modes dynamically, right now the server and client are running in different terminals with the use of a cli
Server info
2025-07-04T16:02:35.592167Z INFO lightyear_Menu_Example: Started Server as main task
2025-07-04T16:02:35.603624Z INFO bevy_render::batching::gpu_preprocessing: GPU preprocessing is fully supported on this device.
2025-07-04T16:02:35.607616Z INFO lightyear_udp::server: Server UDP socket bound to 127.0.0.1:5000
2025-07-04T16:02:44.472456Z INFO lightyear_udp::server: Received UDP packet from new address: 127.0.0.1:4000
2025-07-04T16:02:44.472585Z INFO lightyear_udp::server: Spawn new LinkOf entity=77v1#4294967373 server_entity=76v1#4294967372
2025-07-04T16:02:44.472728Z ERROR lightyear_udp::server: Received UDP packet for unknown entity: 77v1
2025-07-04T16:02:44.573806Z INFO lightyear_udp::server: Received UDP packet from new address: 127.0.0.1:4000
2025-07-04T16:02:44.573972Z INFO lightyear_udp::server: Spawn new LinkOf entity=78v1#4294967374 server_entity=76v1#4294967372
client info
2025-07-04T16:02:44.454673Z INFO lightyear_netcode::client: client connecting to server 127.0.0.1:5000 [1/1]
2025-07-04T16:02:44.456029Z INFO lightyear_udp: UDP socket bound to 127.0.0.1:4000
2025-07-04T16:02:47.519700Z INFO lightyear_netcode::client: client connect failed. connection response timed out
2025-07-04T16:02:47.519847Z INFO lightyear_netcode::client_plugin: Client Netcode(0) disconnected. State: ChallengeResponseTimedOut
which example uses the equivalent to the old ServerReceiveMessage?
@pine cape to use as a key to a map, should i use PeerId or RemoteId?
I have been able to reproduce this intermittently; maybe there is some kind race condition? let me look into it
Probably PeerId? (RemoteId and LocalId are both wrappers around PeerId)
hm yeah it should probably be re-exported
you just have to use lightyear_frame_interpolation directly
it's what i did
sweet!! thank you:)
I found the race condition, but i'm not sure on what a good fix could be
@pine cape what is the new way to send message to a specific room?
ServerMultiMessageSender with Target::Single(PeerId::Entity(room))?
there's no built in way; I guess just MultiMessageSender where senders = room.clients.keys()
hmm, ok
love to hear it:) I can try looking if you point me in the right direction
It's here: https://github.com/cBournhonesque/lightyear/blob/83cd575ddb96dbe7a24578e1375a003fe1ff3280/lightyear_udp/src/server.rs#L186
Frame 1:
- receive packet 1: we insert an entry in the map, then we queue a command to spawn a Link
- receive packet 2 from the same client: we check if a Link exists for that client. It doesn't because the command hasn't run yet, so we remove the entry.
Basically if we receive multiple connection packets in the same frame it causes some issues
having the client spawned in Setup, and then triggering client connect later seems to work:)
i think it was just luck :p
it seems to be working consitantly if that helps you debug the race condition
@pine cape the old ServerReceiveMessage has a from() method to get the sending Peer, but MessageReceiver does not have any mechanism for that, is MessageReceiver the correct alternative to ServerReceiveMessage?
you now have one link per connection; and the RemoteId on that link lets you identify who sent the message
so there are multiple MessageReceivers?
on the server you will have one Link entity per connected client.
Each of these will have Link LinkOf MessageReceiver RemoteId, etc.
any mechanism for quickly identifying links that have messages? triggers maybe?
Messages are similar to bevy Events, they are buffered and you have to check the buffer every frame
if you want to something more trigger-like, you can use https://docs.rs/lightyear/latest/lightyear/prelude/struct.TriggerSender.html
Component used to send triggers of type M remotely.
it will emit https://docs.rs/lightyear/latest/lightyear/prelude/struct.RemoteTrigger.html on the remote
Bevy Event emitted when a TriggerMessage<M> is received and processed. Contains the original trigger M and the PeerId of the sender.
ok, that looks more like it
the MessageReceiver thing, each Link will have multiple, one for each kind of message right? so you would need to iterate over all links to check for messages right? and for each MessageReceiver
yep, although i don't think you read all your messages in one system. It would probably me more like this when you read a specific message: https://github.com/cBournhonesque/lightyear/blob/83cd575ddb96dbe7a24578e1375a003fe1ff3280/examples/simple_box/src/client.rs#L87
and if you know you're on the client you can use Single for faster syntax
i saw that example, and because of that Single i thought that there was only going to be 1 MessageReceiver
but that is for the client
yep, it's just because the client only has one Link entity
components like Connected, Disconnected only remain on the entity for 1 frame?
nope, they stay there
@pine cape I think it would be cool if the simple setup example could actually connect more than one client haha
It is kinda redundant to only be able to connect 1 in a multyplayer crate
Is HostClient still achieved by having both client and server plugins on the same app?
yep, and by having Client and LinkOf on the same entity: https://github.com/cBournhonesque/lightyear/blob/main/examples/common/src/cli.rs#L196
PRs welcome! That example is mostly to show in which order to add the crates and spawn clients, i'd like to keep it relatively simple. But i could see a simple CLI to specify the client id
i have a fix, pushing 0.21.1 now
So for the crossbeam channel, I create it in main and pass it into the client and server plugins through their plugin structs. Maybe I'm being dumb, but does anyone have any ideas on how to get it into a system to I can add it to the server entity? Previously on battle star galatica, I saved the crossbeam channel to a resource and thats how I accessed it in a system, but now its a component not a resource
One solution I can think of is make crossbeamIO derive clone, so i can put it into a resource, but that seems stuborn
Trying to get crossbeamIO from the plugin struct directly into an spawned entity doesn't work without clone either, So I'll send a pull request:)
No rush @pine cape (or anyone else that wants a to use lightyear in seperate mode for a steam game), but I think I have the menu example working besides the crossbeam server & client link. I get a ConnectionRequestTimedOut error when trying to connect. Maybe I followed the test example wrong, but would love to have you take a look if/when you get the chance:)
to test it you would do cargo run -- full and then press play
your client seems to be using UDP and not crossbeam? https://github.com/SueHeir/lightyear-menu/blob/0.21.0/src/networking/client.rs#L68
its removed later:)
so when you enter the Client state, depending on whether you are pressing play or joining a server it should use crossbeam or udp respectively
and then when pressing play I use a second pair a crossbeam connections (not crossbeamIO) to communicate when the server is ready to join, so there is not race conditiion between the server starting and you trying to connect
wow messages are entities now?
It seens we really going for that everything is a entity thing
I cant compile lightyear on my end, I have a pr ready but winit bug is annoying hehe
Is there an idiomatic way to get the ClientId from a ServerReceiveMessage? Trying to determine what to do based on the client that sent the message
There should be a from field: https://docs.rs/lightyear/0.20.3/lightyear/server/message/type.ReceiveMessage.html assuming you're on 0.20
Bevy Event emitted on the server on the frame where a (non-replication) message is received
Ahhhhhhh darn, thanks! I'm still on 0.19.1
I have one last dependency that only (officially) supports Bevy 0.15.3 max
it's also present on 0.19 ๐
Ah client has a bunch of components associated to it
yep; there's a description here https://github.com/cBournhonesque/lightyear/releases/tag/0.21.0
Wait i need to add message sender to my message sending client?
Or is that done via the protocol
really cool, tho opens a lot of doors
You can add it manually; or if you add a network direction for that message in your protocol, it basically does app.add_required_component::<Client, MessageSender<M>>()
Is it a bad idea to do the following in the same system:
commands.entity(entity).remove::<ReplicationTarget>();
commands.entity(entity).despawn_recursive();
tokio poops itself and my client/server apps crash hard
I'm guessing one system to stop replication, and another to despawn the server-side entity with a Removed<ReplicationTarget> filter might be the better way
you're trying to despawn locally without the despawn getting replicated?
I think you can do
commands.entity(entity).remove::<Replicating>();
commands.entity(entity).despawn_recursive();
I'm trying to despawn on both local and remote, but I have sort of a chicken-and-egg problem where if I despawn both locally and remote, the server tries to replicate the despawn to something that no longer exists... so I have to make sure the server does its thing first
I probably messed something up but when I first tried despawn_recursive() on remote the entities (rendered meshes) still persisted on the client
If you want to despawn both on local and remote, you should just despawn the entity on the server
the despawn will be replicated to the client
so just commands.entity(entity).despawn_recursive();
Interesting, that is what I'm doing
I'm pretty sure I'm passing in the right entity. I'll do some more debugging
Gosh I don't know how many times I've done this... my bad haha. I'm pretty sure I'm not mapping the entities.
Always forget to do that
you're sending the entities to despawn in a message?
yes it's kind of error prone; i wish i could make it easier to not forget this
Yeah I'm probably going about most of the networking stuff wrong.
In this scenario I'm sending just a simple newtype message to the server that contains an Entity. The server will despawn that entity and spawn a new one in its place.
My whole mental model of how I'm going to do networking stuff is basically:
- Client sends message to server
- Server updates if message is valid
- Server updates get replicated to all clients
I'm trying not to transfer authority to clients, not because I don't think its a good idea but because I don't understand it or know if it's a better way of doing things ๐
What kind of game are you making? You can just spawn entities on the server with server-authority
A turn-based strategy game. Have you ever played Civilization before? I can give a concrete example of what I'm doing now
Yes i have
I would def just spawn on the server for a turn based game:) makes things simpler from my perspective
*not an expert though, but if you're not worried about real time gameplay (like prespawning bullets) just do as much as you can on the server
Yep I am spawning everything on the server already, but the essence for me is when does the server know to spawn something?
So I have a settler on my screen (client) and I want to press a button, "Settle City". This event should be forwarded to the server somehow so it can found a city in the settler's place. I am forwarding this information to the server via a Message.
Server despawns settler, spawns a city in lieu of it, and replicates the new city to all clients.
I think deterministic lockstep would also work well for a turn based game; it's not supported in lightyear right now, but this https://github.com/gschup/bevy_ggrs handles it.
that's what i'm currently working on
Client -> Server actions should be sent as part of Inputs.
I would put all your inputs in a shared enum like this: https://github.com/cBournhonesque/lightyear/blob/main/examples/simple_box/src/protocol.rs#L95
(or you can also split them across several different input types)
Inputs must implement MapEntities, and any entity stored there will be mapped correctly to the server entity
was just about to recommend using inputs
Okie dokie, I'll make the switch. Thanks ๐
I've been confused about this before
also inputs pressed on the client on tick T are guaranteed to be processed on the server on tick T
Well, I know what I'm working on tomorrow!
let me know how it goes
So I think you could probably make custom inputs that aren't button presses or mouse positions, like maybe a enum of different actions like "settlecity", you'd just have to add them before the client sends the inputs. I think I did that somewhere in my 0.19 lightyear game if you need more details lemme know:)
Yes thatโs definitely what Iโm thinking about doing now. Basically just defining a server API for clientsโฆ
Iโve been having a ton of fun with #1357045538884816966 too. Planning on exposing most if not all of this API to scripts.
Loving the library! Btw it looks like the examples are slightly wrong, or at least they confused me for a bit. I guess the component tuple in Trigger is an "or" rather than an "and" (https://docs.rs/bevy/latest/bevy/prelude/struct.Trigger.html#method.components) so if I want an observer to only run once I need to do something like this:
trigger: Trigger<OnAdd, PlayerId>,
should_trigger: Query<(), (With<PlayerId>, Or<(With<Predicted>, With<Replicating>, With<Interpolated>)>)>,
mut commands: Commands,
) {
if !should_trigger.contains(trigger.target()) {
return;
}
Is there a better way of filtering to the "correct" entity? I also tried Without<Confirmed> but it still gets run multiple times in the client.
Type containing triggered Event information for a given run of an Observer. This contains the Event data itself. If it was triggered for a specific Entity, it includes that as well. It also contains event propagation information. See Trigger::propagate for more information.
For context, I was trying to figure out why I was getting multiple players on the client before understanding what was happening. The simple_box example handles it by having Without<Confirmed>> in the draw_boxes system query, but things like handle_predicted_spawn are still getting run two or three times
Triggers are indeed an (OR), but they only run once for the list of components if an entity contains multiple of them.
(i.e. if you have Trigger<OnAdd, (A, B)> and both (A, B) are added at the same time on the entity, the observer triggers only once)
Oh interesting, thanks that's a good distinction
The reason why some of these observers conditions are complicated is because I try some edge-cases related to HostClient, for which I don't have a great solution for. The main thing i'm missing to improve things is being able to order observers.
If you don't use HostClient, you can just use something like
pub(crate) fn handle_interpolated_spawn(
trigger: Trigger<OnAdd, PlayerColor>,
mut interpolated: Query<&mut PlayerColor, With<Interpolated>>,
)
it's true that it will trigger 3 times (one for Confirmed, Predicted, and Interpolated), but unless you're replicating a lot of entities it shouldn't matter
if you are, you can just switch to using a system instead of an observer
Yeah, it shouldn't matter but I had my setup_player observer creating a new rigid body, so it ended up looking like two separate player entities ๐คฆ
Definitely my mistake lol
My suggestion is more for documentation, adding a comment explaining the observer conditions to the simple_box example might save someone else some time. It's possible it's already explained in the book and I glossed over it
Thanks for the feeback, which part should be explained further? feel free to open a PR to improve the examples!
Specifically that handle_predicted_spawn will get triggered for Confirmed, Predicted, and Interpolated. Sure, I'll send a PR with some additional comments
Worth noting that if run in distinc insert statements it will run twice so try to add in the same chain bundle
Might be a silly question, but I'm reading through the book and saw where a Gizmo is spawned on the server and replicated. Do people typically spawn their meshes server-side this way and just have them get replicated to the client? If so, even if the server is headless?
no i think the meshes are only spawned on the client
Okie dokie, that's what I thought. Thank you
Moving over to Inputs now. If there is no entity with an ActionState<I> component in the world, what is that indicative of?
InputPlugin::<MyInputs>::default() is registered.
Trying to buffer them on the client but single_mut for the component fails
You have to insert an InputMarker<MyInputs> on the entity that you want to control: https://github.com/cBournhonesque/lightyear/blob/b1ef078d12b027c66093ed46d699ed1ca548dc0c/examples/simple_box/src/client.rs#L111-L111
This will insert an ActionState<I> for it
that part is a bit unclear, i admit
Ok thanks, I am a bit confused about the whole Inputs paradigm in that sense. So this "entity that I want to control" should be a singleton? I'm inferring this because of the get_single_mut() in the example, but perhaps that's just in the context of the simple box example where I'm assuming there is literally only one entity to control.
For my use case I'm trying to tether inputs to elements in the UI. Would I want to add an InputMarker<MyInputs> to each UI element capable of processing inputs?
I'd probably just have one entity, with like a player component, player score compoent, etc. And also have the inputs there all in one place
and then when you press a button, you can set the action_state with the input you want, kinda like here I have a function for mouse position:
pub fn global_mouse_input(
windows: Query<&Window>,
query_view: Query<(&Camera, &GlobalTransform), (With<Camera2d>, With<OuterCamera>)>,
// mouse: Res<ButtonInput<MouseButton>>,
mut action_state_query: Query<
&mut ActionState<PlayerActions>,
(With<Predicted>, With<Controlled>),
>,
) {
let window = windows.single();
let (camera, view) = query_view.single().expect("no camera view?");
if let Some(world_position) = window.expect("no window")
.cursor_position()
.and_then(|cursor| Some(camera.viewport_to_world(view, cursor)))
.map(|ray| ray.unwrap().origin.truncate())
{
for mut action_state in action_state_query.iter_mut() {
action_state.set_axis_pair(&PlayerActions::MousePos, world_position);
}
}
}
app.add_systems(
FixedPreUpdate,
global_mouse_input
.before(InputSystemSet::BufferClientInputs)
.in_set(InputManagerSystem::ManualControl),
);
Right exaclty, I understand that. That's what I'm going for. My question is really one about architecture... it's just not really intuitive to me why it's a singleton. It just kind of is in the example/book
hmmmm, I'm not sure I have a anwser to your question then, I've always just had one ActionState per player. I think it helps you know which player the actions are from, not sure if under the hood lighyear can have more than one actionstate per player
That's just in the context of that example. The idea is that you attach inputs on an entity to specify which buttons will let you control that specific entity
You could have 2 entities, and WASD controls entity 1, and the UpLeftDownRight controls entity 2
I don't really have support for 'global' inputs that are not attached to an entity right now. My plan is that for those you would attach the InputMarker to the Client entity iself
but for now I think you just have to create a mock entity that will hold your inputs
for example this is how it could look like: https://github.com/cBournhonesque/lightyear/blob/cb/0.20.3/examples/avian_physics/src/client.rs#L60
if you had multiple entities to control
Got it, thanks! And one last question:
I'm making this entity locally? The server will pick it up somehow?
I already have Player entities that are spawned on the server and replicated to all clients. Should I insert the InputMarker<MyInputs> component remotely or on the locally relicated entities? Simple box seems to do it client-side
Good question, you can try it but I don't think it will work because the server doesn't know about the entity.
The entity should be spawned by the server and replicated to your client
and you add InputMarker on the local entity (Replicated or Predicted) on the client that you want to control
Okie dokie, thanks ๐ I'll trudge on
Thanks for being so responsive, both you and @peak ice
has anyone gotten steam to work for p2p connections in 0.21.0?
yes, just uncomment the 'steam' code in the examples, like so https://github.com/cBournhonesque/lightyear/pull/1072/files
i might have some questions soon because I'm running steam in separate mode:)
But this might also be a problem for next weekend
Oh the room manager died, question network relevance? Is now the component network visibility right?
oh servermultimessage sender is gonna be useful
So previously, I had steam in this weird Arc parking_lot to get it to both the server and client in separate mode
let steam_client: Arc<parking_lot::lock_api::RwLock<parking_lot::RawRwLock, SteamworksClient>> = Arc::new(RwLock::new(SteamworksClient::new_with_app_id(480).unwrap()));
Now, we have
let (steam, single_client) = lightyear::prelude::steamworks::SingleClient::init_app(480);
I believe I want both the client and server to process steam callbacks when hosting, but single_client needs to be added as a non-send resource so I can't send the server app to a separate thread as of right now.
Any ideas on how to tackle this problem?
steams handled by areonet now right? Should I contact that person about it?
Maybe you can just wrap single_client in a fake 'send' resource?
i don't think aeronet_steam actually uses single_client
So I've tried just moving steam and not single_client, but I think this code is required to get steam server to accept p2p request.
.insert_non_send_resource(steam_single)
.add_systems(
PreUpdate,
|steam: NonSend<lightyear::prelude::steamworks::SingleClient>| {
steam.run_callbacks();
},
You're recommending putting steam_client into a arc<mutex<>>> and then after the move pulling it out and appyling the above code? and then pray it works?
yes, i think that the single_client doesn't actually required 'Send'
I used to wrap it in a SyncCell: https://github.com/cBournhonesque/lightyear/blob/cb/0.20.3/lightyear/src/connection/steam/steamworks_client.rs#L10
sweet, thank you:)
wait actually I'm a little confused, do i need to change lightyear back to SyncCell? or is there a way around it with the use of SyncCell? Can we have single_client derive clone?
ooooh single_client is defined in steamworks not lightyear
yes, you have to supply your own steamworks client
so you can do whatever you want with it
what do you mean by supply my own steamworks client? like fork steamworks and change single_client to clone?
no i mean that lightyear doesn't create it for you
you create it yourself using steamworks rs
sorry for the many questions, feeling very confused about what changed. So I import steamworks in my toml file, init steamworks, but then I'm still stuck with a singleClient I can't clone or move without a mutex
Sorry I know this isn't exactly lightyear related, more of a me issue with rust lol
I think the steam.run_callbacks() system needs to run in only one of the apps
it's basically just something that needs to run regularly
so you might not even need to send it another app or clone it or anything
ok:) thanks for the help and encouragement, I'll make sure to try having it only run on one of them:)
alrighty I think its working!! I got a quick and easy question for ya now:) Encountered an error in observer lightyear_udp::server::ServerUdpPlugin::link: Address already in use (os error 48), Does stop server trigger not close the Udp?
this bug happens if I press Play, press quit (which disconnects me and stops the server), and then press play again
nope, Stop server stops the netcode connection, but not the underlying IO
to stop the IO, you would have to trigger Unlink
But just triggering Start/Stop should work with Udp, that's what i do in the examples
I always keep the Udp io open
I can't seem to find where you stop the server and start it again in the examples
it's a bit hidden https://github.com/cBournhonesque/lightyear/blob/b1ef078d12b027c66093ed46d699ed1ca548dc0c/examples/common/src/server_renderer.rs#L103-L103
thank you:) very curious as to why its not working for me, do you get these warning. when you stop the server?
2025-07-06T21:34:27.389732Z INFO lightyear_Menu_Example::networking::server: Server received StopServer command
2025-07-06T21:34:27.389754Z INFO lightyear_Menu_Example::networking::server: Server Stopped
2025-07-06T21:34:27.389774Z INFO lightyear_netcode::server_plugin: Stopping netcode server
2025-07-06T21:34:27.390041Z WARN bevy_ecs::error::handler: Encountered an error in command `<bevy_ecs::system::commands::entity_command::despawn::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: The entity with ID PLACEHOLDER does not exist (enable `track_location` feature for more details)
2025-07-06T21:34:27.409763Z INFO lightyear_netcode::server_plugin: Disconnection from netcode client 1. Despawning entity.
2025-07-06T21:34:27.409799Z WARN bevy_ecs::error::handler: Encountered an error in command `<bevy_ecs::system::commands::entity_command::despawn::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: The entity with ID 98v1 does not exist (enable `track_location` feature for more details)
2025-07-06T21:34:30.380036Z INFO lightyear_netcode::client: client connection timed out
2025-07-06T21:34:30.380082Z INFO lightyear_netcode::client_plugin: Client Netcode(1) disconnected. State: ConnectionTimedOut
no, haven't seen that
I think it was a race condition between closing the server at the same time as disconnecting, but we got it fixed. I'm pretty sure everything is working now:) If anyone is looking for a starting point for a steam game running in separate mode I like github stars: https://github.com/SueHeir/lightyear-menu
thanks for making this btw! i haven't tried to get separate mode working, so it's great to see that it works
I've updated the rollback/correction logic following some tips from Joy and now it looks much much smoother than before!
Super happy about that result
Now doing deterministic-replication like GGPO where we only replicate inputs seems feasible, since the prediction/correction works very well
@wintry dome also when predicting other clients, i now fetch by default the latest input available in their input buffer, so no need to do the complicated logic from spaceships anymore
i'd really like to understand what's going on with the blinking/disappearing gizmos, though
By any chance are you talking about this kind of logic? https://github.com/cBournhonesque/lightyear/blob/ddf6bc38db162d0723e053abbfe9977a4da7307e/demos/spaceships/src/client.rs#L156
yep!
Mad ๐
Thank you for sharing this, I was just about to start trying to integrate Steam into my project!
@pine cape what is the alternative for ServerReplicate now?
@pine cape on the client, which component marks the singleton? LinkOf?
is there a better way?
network_state: Single<
(
Has<Connected>,
Has<Connecting>,
Has<Disconnected>,
Has<Disconnecting>,
),
With<Client>,
>,
might be a gizmo context thing
is the rollback using a custom schedule?
i added some stuff to the gizmos a while ago to try to remove flickering in FixedUpdate, but if its using something outside of that, you might want the same solution for your own
The rollback is in PreUpdate, and it manually runs FixedMain several times. Otherwise the gizmos are being updated in PostUpdate.
You added that to bevy, you mean?
ya, odd then, should be fine if its in post update
@pine cape is the issue happening on main, or just on that branch?
nvm i see its on main rn
is there a way to not replicate a specific component on an entity to a single client, and replicate it to the rest normally?
let mut override = ComponentReplicationOverrides::<Position>::default()
.disable_for(sender);
API documentation for the Rust ComponentReplicationOverrides struct in crate lightyear.
@pine cape If you had a crated project, do you think it would be wise to have a central state manager, or modularize each state for each crate. For example: Save state runs in crate save, and completely ignores asset state in crate asset
What do you mean by state, a bevy State?
Yes, same project, I was creating a ServerStates to organize the startup of the server, WaitingForAssets -> WaitingForServer -> WaitingForSave -> ServerRunning
Yappy rather have each sub crate control when they are initialized
I'd probably start with a single ServerStates, seems simpler
i am gonna shot you
hahaha jk
I'm getting a strange bug, i think related to bei trigger handling. (0.21.0 and main)
2025-07-08T21:33:11.306335Z WARN aeronet_io::packet: 538v1 has 2 received packets which have not been consumed - this indicates a bug in code above the IO layer
I get a client but not a connection.
This happens when i don't have an observer running on a Fired trigger, e.g adding this trigger observer removes that warning and the client connects just fine:
app.add_observer(impulse_jetpack_client);
fn impulse_jetpack_client(
trigger: Trigger<Fired<EvaMove>>,
) {}
I have this on startup:
app.add_plugins(InputPlugin::<EvaContext> {
config: InputConfig {
rebroadcast_inputs: true,
..default()
},
});
app.register_input_action::<EvaMove>();
Thinking about this.. will Trigger work with rollback since it won't be called in FixedUpdate? Or is BEI calling the Trigger itself?
aren't triggers called regardless of how the event was activated
Bei will run during rollback and will resubmit the trigger
Are you sure about that? I'm very surprised that handling a trigger would remove that error, that error is very close to the raw IO
will try make a reproduction
I have an example called bevy enhanced inputs if you want an easy starting point
Yeah I'm using that! Works perfectly so far ๐
Can't reproduce it.. I'll leave it for now. I must be doing something strange. ๐
I get a client but not a connection
What do you mean by this? This has to be related.
Lightyear does not support not using Netcode right now
As in the server calls when the client connects: pub(crate) fn handle_new_client(trigger: Trigger<OnAdd, LinkOf>, ...) { but not fn handle_connections(trigger: Trigger<OnAdd, Connected>, ...)
The Connected trigger does work when I add that Trigger<Fired<EvaMove>> observer.
Pretty strange! (I am using Netcode + WebTransport)
Here's some trace logs: https://gist.github.com/gak/5489459c146151a4f6797c0b3f16c219
The client times out on line 361
Not a big deal! it works fine with enabling the hook ๐
Those "Recomputing" logs also happens when it connects properly
I need to fix those Recomputing input delay logs, they are annoying lol
I don't get the issue in your logs, I don't see
2025-07-08T21:33:11.306335Z WARN aeronet_io::packet: 538v1 has 2 received packets which have not been consumed - this indicates a bug in code above the IO layer
Thanks for investigating anyway. If I see it again with some other code, I'll check it out again and try a proper strip down of my project to replicate it.
@pine cape do you know Path of Exile? they have on the client a toggle to choose between Prediction and Lock Step, Lock Step is basically Confirmed only, does lightyear has anything like this?
I don't think lock step is Confirmed only. I think lock step is setting the input delay to the rtt of the player, to avoid doing any prediction
You can do this in lightyear by updating the InputConfig
is the entity marked by should be predicted the entity that is predicted?
i ask bcs right now i have a shouldbe predicted entity but no predicted entity womp womp
That's interesting, don't think I've ever seen a game with an option like that. I think WoW had an option to set a lag tolerance to accommodate high/inconsistent latency, but that's essentially more prediction, not less
No, it's the Confirmed entity, and a Predicted entity should be spawned for it
ah bummer
now i have crazy bug just lovely
The game basically freezes any time a frame is not received, and only continues the simulation on the next confirmed frame
Yeah that's definitely lockstep with input delay
I'm working on adding deterministic replication, including lockstep
@pine cape so, we are getting 2025-07-09T21:17:00.187598Z ERROR lightyear_replication::send::components: ClientOf 153v1#4294967449 not found or does not have ReplicationSender, but looking at the inspector the entity does exist and have ReplicationSender, BUT it might be because we are adding things on a system with Trigger<OnAdd, Connected> and those commands are run before the component is properly added to the component, do you know anything about that?
2025-07-10T01:35:50.584053Z DEBUG lightyear_prediction::registry: check history_value=Some(Updated(Position(Vec2(0.0, 0.0)))) confirmed_component=Some(Position(Vec2(-4.3987303, 1.9698288)))
2025-07-10T01:35:50.584070Z DEBUG lightyear_prediction::registry: Should Rollback! Confirmed value is different from history value
Thanks for adding this! Very helpful ๐
is there support for automatically syncing events across the network?
events that are not triggers?
you would have to register them as messages and then handle the message->event-buffer transition
@pine cape there is no tag for 0.21.1
i'm getting 2025-07-10T14:10:04.519882Z ERROR lightyear_transport::plugin: error sending message: ChannelNotFound, is anything other than add_channel needed?
Hi ๐ just put a PR up to fix the RemoteTimeline syncing https://github.com/cBournhonesque/lightyear/pull/1085
.add_direction(NetworkDirection::Bidirectional)
Adding the direction
Thanks that's awesome, I'll take a look!
- It is possible there is a problem because remote timeline also upgrades based on FixedTime progressing, however it is still also updated from receiving remote packets so it should balance out. I would be interested in seeing your test cases where you can manually create an issue
- Honestly I just switched to offsets because Claude recommended it for added precision. It is possible that it would be equivalent to just updating the time directly, I would have to think about it.
Empirically when I enable trace logs things do seem to work (if we're ahead we slow down and get back in sync)
I would love to have some unit tests/integration tests for this but they are hard to write
(also fixed typo in hierarchy.rs that broke compilation) @dry flower chad
Yeah, no kidding. And I'm pretty new to rust so figuring out how to hook everything up for an integration test like this might be an adventure. But I'm sure Claude will have some ideas! and I see some commented out tests in lightyear_tests/src/timeline/sync_tests.rs that might be a good place to start
I'm struggling to repro the virtual time issue now that TickDelta addition is fixed, but also while trying to do that I ran into a crash that sure looks like another TickDelta edge case
ah-ha, yep
let small_delta = TickDelta::new(0, Overstep::from_f32(0.5), false);
assert_eq!(TickDelta::from_i16(0) + (-small_delta), -small_delta);
assertion `left == right` failed
left: 65535:0.50
right: -0:0.50
Left: 65535:0.50
Right: -0:0.50
my 2c, I think an implementation based on unsigned deltas + a neg: bool is going to be hard (and ugly) to get right. I assume your reason for splitting that type into tick_diff and overstep (rather than use some floating-point type for the whole thing) is so that large TickDeltas don't have worse resolution, which is reasonable, but I would at minimum make tick_diff an i32 and get rid of neg, I think that would make the math functions a lot simpler to implement
or better yet, you could use something like a 48.16 fixed point (48 bits of tick_diff, 16 bits of overstep)
assuming my math is right, at 64tps that would give us a resolution of 238ns for the overstep and a range of +- 69,684 years. And basic arithmetic operations are a single cycle which is hard not to like.
I just split it into tick_diff and overstep since it seemed more natural to work with, since the time is primarily expressed in terms of tick.
Also i wanted to reduce the data transmitted by eventually switching the overstep to 8 or 16 bits
oh I see, PositiveTickDelta goes in some network messages
Only the Pong, so admittedly it doesn't really matter
i'd be open to any of the changes you suggested to simplify this code!
I'm with you on the 16 bit overstep (8 is maybe a bit coarse) and I was gonna say 16 bits seems a bit small for the tick_diff, but if you expect the value to always be small, that seems fine
The tick_diff can go up to u16::MAX
right, if tick_diff stays 16 bits then RemoteTimeline needs to set self.now at startup rather than self.offset
because self.offset (which is a TickDiff) would overflow if a new client joined a server that had been up for more than (2^16/64/60) ~= 17 minutes
ok I'll play around with a couple of different things and see what fits best with the rest of the code!
I'm not sure I follow
ticks wrap around at u16::Max, so it should be ok no?
oh I get what wrapping_id is now ๐คฆโโ๏ธ
yeah that seems fine, there's no reason ticks would have to be monotonically increasing with no wraparound
I'd be curious to see how all of these clock sync algorithms hold up around that 17 minute mark though
just need to make sure it's handled that way consistently everywhere
But yeah it's a great point that the RemoteTimeline is currently updated from how fast Time<Virtual> is going (the offset is an offset compared to the current timeline tick, which is incremented every FixedUpdate) which causes some weird effects
Yep i don't think I handle them correctly everywhere but in replication I do a regular tick_cleanup every 5 minute: https://github.com/cBournhonesque/lightyear/blob/03c82b5f3f6a9f1528eb3e42418f5b38bd8d033a/lightyear_replication/src/receive.rs#L319-L319
Yeah, like I said I was really struggling to get a repro now so the issue was probably 95% TickDelta math, but I still think Time<Virtual> is probably wrong
My claude was very insistent about that and managed to convince me hahaha
Haha; well my claude is the one who suggested the offset
that being said I hadn't considered the time offset between advance_remote_timeline and update_remote_timeline / the fact that you might get multiple packets, or no packets, in between two advances
I reckon what we actually want is to advance our estimate using an up-to-date real-time clock at the start of every update()
say you get a FixedUpdate (advance_remote_timeline) on tick 100 and then late into that tick, like 0.9 overstep, you handle a packet from the server and calculate a new_estimate (accounting for network delay) that says the server is at 100.9
At that point you're perfectly in sync, you don't want it to look like your error is 0.9, but I think with the current system your now would still be set to 100.0 until the FixedUpdate a couple ms later
I reckon what we actually want is to advance our estimate using an up-to-date real-time clock at the start of every update()
agreed
All received packets are handled in PreUpdate, before any FixedUpdate runs
Another thing is that the packets currently only contain the Tick at which they were sent.
For better precision maybe we should include the overstep. However I'm not sure how helpful the overstep actually is; the time is updated at PreUpdate so the overstep mostly depends on the PreUpdate time, not the real time at which the packet is actually sent in PostUpdate.
Yeah, I think including the overstep could be nice, but agree that we'd have to always get the most up-to-date time. I'm new to all this so I definitely need to review how bevy's clocks work
All received packets are handled in PreUpdate, before any FixedUpdate runs
This is probably another thing that's not great. The server sends packets in PostUpdate after the FixedUpdate has run and the ticks have been incremented. But the clients handles the packets in PreUpdate before the FixedUpdate has run, so any tick comparisons might be skewed
The client seems to like to hover around a tick and a half ahead so that could very well be the reason why
that's not a bad thing according to that overwatch talk, or was it the rocket league one, idr
but probably better to control that explicitly instead of it being a happy accident
The client is a tick and a half ahead after accounting for RTT, you mean?
yeah I guess what I mean by that is that when looking at the server gui I feel like I see synced up clients having a tendency to flicker between 1 and 2 frames buffered
I think that's what the number next to client nametags means right
that's probably because https://github.com/cBournhonesque/lightyear/blob/main/lightyear_sync/src/timeline/input.rs#L247-L247 the client is trying to be RTT/2 + stddev + 1 ticks ahead
oh right, the 1 tick
so the 1-2 ticks is just because i take stddev + 1 as margin
yeah i've been staring at the remotetimeline so much that I forgot about the inputtimeline
Anyway I'll play around with using something like a 16.16 fixed point for tick+overstep because I feel like that would simplify a lot of code
I've summarized the issues here: https://github.com/cBournhonesque/lightyear/issues/1086
feel free to comment out more
is there a way to delete In use client ID's on the server once it is stopped?
I am getting this warning WARN lightyear_netcode::error: Netcode error: ClientIdInUse(Netcode(1)), but then eventually it says the client disconnected, and then it lets me connect again. (crossbeam separate mode stuff)
it's probably a bug; on stopped the netcode internal state should be reset so that new packets that reference old connected clients don't hit errors like ClientIdInUse
oooh, could the client which is reusing the same crossbeam channel be the issue? Like does a client link store data that would make the server think its the old connection? Or is this all server side?
oooh actually I messed up
I had two server side crossbeam LinkOf
for some reason I have to respawn the crossbeam server link entity every restart, but it seems to be working without warning now
@pine cape on the server i am using action_state.just_pressed(Action), but since 0.21 it rarely is true, if i spam i get some frames where it is true
and the state changes to JustReleased even when you are holding the button
This is a problem in main or in 0.21? I recently changed something related to leafwing ordering which can cause some issues, I thought it would be safe though
0.21.1
this broke my dash logic :<
commands.spawn((
Room,
RoomOfEntities::default(),
RoomOfClients::default(),
));
?
The input ordering was only changed after 0.21.1 (https://github.com/cBournhonesque/lightyear/commit/56cfb0a8d71301524e499aedba0300d75d150bab#diff-94faa688e137f4bc8be150ab8543ba55c43ec4bb04c0ed81cab4469a99d30aa8), so you shouldn't see any issues in 0.21.1
Hey, are the examples in the github repo for the latest version 0.21?
Because I've been trying to get it to work for quite sometime and I cant understand whats wrong. I tried looking at the book for help and while its good, only helps in creating a window.
If someone could point me to a good learning resource that would be very helpful. Thanks
The example in the github repo are for the main branch, which is very similar to version 0.21
What part doesn't work? I've tested the examples, they do work
im trying to follow the simple_box project.
There are imports from outside the simple_box project folder so im a little confused.
I was able to understand the errors I had earlier however I have import errors now.
- failed to resolve: use of unresolved module or unlinked crate
lightyear_examples_common
use lightyear_examples_common::shared::FIXED_TIMESTEP_HZ; - failed to resolve: use of unresolved module or unlinked crate
lightyear_examples_common
use lightyear_examples_common::cli::{Cli, Mode}; - failed to resolve: use of unresolved module or unlinked crate
lightyear_examples_common
use lightyear_examples_common::shared::SharedSettings;
The lightyear/examples/common folder has cli and shared
for shared its a pretty easy fix to just copy them but the cli file has other imports from within that folder so cant copy it...
Yes the examples depend on this other crate for setup: https://github.com/cBournhonesque/lightyear/tree/main/examples/common
If you want a simpler self-contained example you can look at: https://github.com/cBournhonesque/lightyear/tree/main/examples/simple_setup
So ill need to download this as well?
I did try this however i wanted to try it with players and input so
ah i think i didn't push a new version of that crate, let me do that
Thank you ๐
done
This strange one has come back, but now if I add another observer to Trigger<Fired<X>> (bei) that error comes back on the server and the client times out connecting. i.e. this works:
pub(super) fn plugin(app: &mut App) {
app.add_observer(|trigger: Trigger<Fired<EvaMove>>| {});
}
And this doesn't
pub(super) fn plugin(app: &mut App) {
app.add_observer(|trigger: Trigger<Fired<EvaMove>>| {});
app.add_observer(|trigger: Trigger<Fired<EvaMove>>| {});
}
Then this does work:
#[derive(Event)]
struct TestTrigger;
pub(super) fn plugin(app: &mut App) {
app.add_observer(|trigger: Trigger<Fired<EscapeCurrentView>>| {});
app.add_observer(|trigger: Trigger<Fired<EscapeCurrentView>>| {});
app.add_observer(|trigger: Trigger<TestTrigger>| {});
This is doing my head in lol.
I can't replicate the problem in the bei example (main branch). Also different combinations of observers cause it and others don't. It's very strange.
I guess it's time to dig in ๐
commenting out all app.add_plugins(InputPlugin::<EvaContext>.... and app.register_input_action::<..> doesn't make a difference either. Maybe not even related to input.
You mean that you still get errors like
2025-07-08T21:33:11.306335Z WARN aeronet_io::packet: 538v1 has 2 received packets which have not been consumed - this indicates a bug in code above the IO layer
?
yeah
I THINK this is it based on not getting any of these errors and trying a bunch of things.
- I was loading the
EnhancedInputPluginalong with aadd_input_contextto a context that isn't used in multiplayer, which is run before any (lightyear)InputPlugins. - If I comment out the EIP plugin and move the add_input_contexts to AFTER
InputPlugincontexts are loaded, it has so far worked 100% of the time regardless of the combination ofadd_observers
I can't really see why this would be a problem since InputPlugin does load EnhancedInputPlugin then add_input_context anyway. Maybe system ordering, but again, it is set up after the EIP plugin.
Still can't replicate it on a lightyear example ๐ซ
Alright, so far the current solution for my setup, not knowing why: Don't manually load the EnhancedInputPlugin yourself.
This repo is a little more complicated and it setup for something called separate mode (server runs in a second thread and is communicated with via crossbeam channels) https://github.com/SueHeir/lightyear-menu, But is a working lightyear example without the common package if thats what your looking for:)
@pine cape rooms are supposed to be a way to filter visible entities right? i have entities that are in other rooms being visible
Maybe you're using incorrectly? Take a look at the network visibility example
NetworkVisibility component
@pine cape would this be viable?
then RoomEvent::AddEntity would be actually Trigger<OnInsert, EntityInRoom>
or is there cases where an entity is in multiple rooms?
Maybe; is it because you would want to inspect the EntityInRoom component?
did you find a solution to this? also getting a lot of warnings like this one
Nope, still getting them
Hmm it seems that ReplicateLike is added automatically to all children of a replicated entity. I'm able to get rid of the warning by adding DisableReplicateHierarchy
that way you have a bidirectional way to look up, both the room of an entity, and the entities of a room
Hm, that might require a ManyToMany relationship as an entity can belong to multiple rooms, which is a bit tricky to do in bev y right now
sadge
Took me a while to realise that it's channels that also need a specified direction, not just messages
would it make sense to add a must_use hint for the add_channel method?
The thing is that it's not really must_use, you can also just add a channel sender/receiver to a transport independently from it being a client or server.
But I agree that this is error-prone
Most of the examples use a Client Server topology but I hope to support P2P in the future
I can update the ChannelNotFound error message to say 'did you forget to call add_direction?'
That would be great as well, thanks!
How can I setup interpolation and prediction for immutable components?
Seems like it's supposed to be supported in 0.21: https://github.com/cBournhonesque/lightyear/pull/1015
but the add_prediction and add_interpolation methods are still unavailable for immutable components, since the SyncComponent: Component<Mutability = Mutable> bound still expects mutable components
am I missing some other way to register components for prediction and interpolation?
It is not possible right now, that PR only enables immutable components for replication
That's one of the main reasons why RelationshipSync exists
Instead of simply registering the relationship component directly
You need to add prediction/interpolation for an immutable component with mode Once or Simple?
@pine cape Why does component that get synced once need the synccomponent? :>
What part of SyncComponent is the issue, PartialEq?
ah
i get the error now
It warns me, it doesnt implement SyncComponent. When in reality it didnt implement clone
Once
though I can see needing Simple as well in theory
is there a quick way to map from PeerId to the entity already built-in?
There is a ReplicationPeers resource
Lightyear
Should I use it to get immutable components replicated? I'd appreciate a short example on how to get that working
Pretty sure it's actually PeerMetadata
Unless it changed since I asked a couple weeks ago.
Sorry, that's right! I'm on my phone so couldn't verify
I think the only way currently might be to wrap your immutable component into a mutable component. Is your component a relationship?
@pine cape are you using some api to make the connection stuff ? iam trying make my own and iam using Tokio @pine cape
What connection stuff? the io layer?
tcp udp
I just use the standard library udp sockets
@silent patrol I have a commit to allow immutable components to be predicted/interpolated. Will push it tomorrow
thread 'Compute Task Pool (3)' panicked at /Users/rherv/.cargo/git/checkouts/lightyear-16a1ca81dacb5ed5/721fbe6/lightyear_prediction/src/rollback.rs:439:13:
attempt to add with overflow
Got this error in the latest commit
Oh.. should be (tick + 1000).0
@pine cape what is the way to register a immutable component?
more precisely, add_predition and add_interpolation
is the content of the immutable component always replicated to the Predicted/Interpolated entity from the Confirmed?
I'm adding it here: https://github.com/cBournhonesque/lightyear/pull/1105
This also unblocks deprecating RelationshipSync, since users can now directly do
app.register_component::<R> on their relationship components
It will be add_immutable_prediction for now
But I think I'll rework that api to be more strongly typed
hmm
Any ideas on how I can make an entity, inherit the visibility of it is "parent"? Do I just add the parent NetworkVisibility component on the child?
My use case is I want the related entities to be replicated to the same rooms as the origin, but not follow the exact replication type
What kind of replication behaviour do you want to override on the child
You should be able to just add ReplicateLike on the child
I think it's added automatically
I think this could overflow, too! you'd probably want tick.saturating_add(1_000).0 if you just want a high tick
...though this still wouldn't work right if tick is near u16::MAX; it might be better to introduce a more consistent mechanism for mismatches - maybe with a TickInstant..? buuut. that depends on your intended use of Overstep
Yup i just didnt have hierarchy enabled
Another question:
fn handle_weapon_carrier(
trigger: Trigger<OnAdd, WeaponCarrier>,
query: Query<&Predicted>,
mut commands: Commands,
) {
let entity = trigger.target();
if query.get(entity).is_ok() {
commands
.entity(entity)
.insert(WeaponInputs::default_input_map());
}
}```
Why triggers like this dont work? It feels like I still need to use added systems
Might be a race condition. My program got a lot smoother after I replaced observers with Added filters
Tick handles overflows gracefully by wrapping around u32::MAX ๐
this should work, since the component gets synced to the predicted entity after Predicted is added
I was thinking of switching from add_prediction(prediction_mode) to
add_prediction_full()
This would give us a strongly typed API where for example you would not be able to call should_rollback if the prediction mode is not Full
I've tested it on my project where I use the Once mode, works great! also left a couple of minor comments regarding the bevy_egui usage in the examples
I'm a bit confused, the tutorial seems to mention ServerPlugins and ClientPlugins as necessary plugins, but none of the examples seem to be using them? Especially the simple box example the tutorial is recreating. What's up with that? Is one or the other out of date?
They are added as part of examples/common. You can also look at the simple_setuo example for a self-contained example
Oh right, ofc! That makes sense, cheers ๐ซก
@pine cape I think I found another bug, unless I got this process wrong. Client pressed escape -> Client triggers client disconnect -> Client disconnects -> Client joins server again -> The following crash:
thread 'Compute Task Pool (2)' panicked at /Users/elizabethsuehr/.cargo/git/checkouts/lightyear-16a1ca81dacb5ed5/4324f45/lightyear_udp/src/lib.rs:163:46:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `lightyear_udp::UdpPlugin::receive`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
I re-add the UdpIO on the client side between the first and second client connects
@pine cape What a refactor mr peri, truly lovely
It's possible. My tests were only disconnecting the connection but without stopping the link. It looks like you're resetting the link. Do you want to open an issue? It shouldn't be a hard fix if you want to give it a go
Yeah Iโll try and fixing it, knowing itโs got something to do with the link reset helps a lot!
2025-07-18T18:45:55.609616Z ERROR lightyear_interpolation: Could not find the receiver associated with the interpolated entity 910v13#55834575758
@pine cape Any idea on what this error means, occurs everytime I despawn a interpolated bullet entity. In a room
i'd say that it is because our bullets have a too small timetolive
removing Link and Linked components before re-adding the IO seemed to fix the issue
yeah it's usually something like a component retaining state when it shouldn't
Hm i can make the log less noisy, i wouldn't worry about it
actually maybe it's a bug? it means that the entity does not have Replicated when you despawn it
I'm pretty close to a new release with all the rollback fixes
The only blocking thing is I want the avian-3d example to be smooth like the avian-2d example is
I'm not too sure yet what's causing the jitters in avian-3d
The main difference is that entities are displayed using Transform instead of Position/Rotation
I cannot release a new version because I get these errors when trying to publish lightyear_deterministic_replication
error[E0432]: unresolved import `bevy_ecs::system::ParallelCommands`
--> /Users/charles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lightyear_transport-0.22.3/src/plugin.rs:18:14
|
18 | system::{ParallelCommands, Query, Res},
| ^^^^^^^^^^^^^^^^ no `ParallelCommands` in `system`
error[E0107]: struct takes 3 generic arguments but 2 generic arguments were supplied
--> /Users/charles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lightyear_transport-0.22.3/src/packet/header.rs:105:29
|
105 | sent_packets_not_acked: IndexMap<PacketId, Duration>,
| ^^^^^^^^ -------- -------- supplied 2 generic arguments
| |
| expected 3 generic arguments
|
note: struct defined here, with 3 generic parameters: `K`, `V`, `S`
--> /Users/charles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/indexmap-2.10.0/src/map.rs:93:12
|
93 | pub struct IndexMap<K, V, S> {
| ^^^^^^^^ - - -
help: add missing generic argument
|
105 | sent_packets_not_acked: IndexMap<PacketId, Duration, S>,
| +++
Some errors have detailed explanations: E0107, E0432, E0599.
For more information about an error, try `rustc --explain E0107`.
error: could not compile `lightyear_transport` (lib) due to 4 previous errors
I truly don't get why since I successfully published lightyear_transport
I only get those errors with this crate
hm it might be due to misconfigured features
ParallelCommands is std only
i tried saying that on the bevy subcrates PR but didn't pass the information properly
but there are a few comments on justfile
Quick question, I am making a character controller who has a rigid body for physics and a child sensor collider as a hit box, but i am running into an issue where the child's position is not propagated from its parent. I am only seeing this propagation error when there are hierarchy of colliders. did a bare minimum test on the head of avian3d with one of the examples and the children collider followed as expected. then did the same test on the avian 3d example in the light year repo and it did not propagate. I re enabled the sync plugin and it still did not propagate. Have you ran into this?
With this change, the RelationshipSync plugins are not needed anymore.
๐
What is the intended way to map entities inside of messages?
Does anyone know why my shared system is running twice?
if !action_state.pressed(&SpacecraftAction::Shoot) {
continue;
};
match weapon_manager.current_weapon_mut() {
None => {}
Some(weapon) => {
match weapon {
Weapon::Railgun {
last_fire_tick,
cooldown,
max_speed,
name,
} => {
let wrapped_diff = *last_fire_tick - current_tick;
if wrapped_diff.abs() <= *cooldown as i16 {
continue;
}
warn!("last fire tick: {}", last_fire_tick.0);
*last_fire_tick = current_tick;
warn!("new last fire tick: {}", last_fire_tick.0);
let shooting_index = weapon_manager.shooting_index;
weapon_manager.increment_shooting_index();
let salt = salt_from_client_index(
character_marker.0.to_bits(),
shooting_index,
);
warn!("entity: {} tick: {} with salt: {}", entity, current_tick.0, salt);
Its added like this in the client:
app.add_systems(
FixedUpdate,
shared_character_firing.run_if(not(is_in_rollback))
);
and in the server:
app.add_systems(
FixedUpdate,
shared_character_firing,
);
It produces these logs on the client when fired once:
last fire tick: 0
new last fire tick: 3065
entity: 282v1 tick: 3065 with salt: 3921178
last fire tick: 0
new last fire tick: 3067
entity: 282v1 tick: 3067 with salt: 3921178
It seems like even though WeaponManager is edited it gets reverted for some reason? I just started having this issue after updating to newest release.
I couldn't post the whole function but it pretty much the shared_player_firing system in the spaceships demo.
You call add_map_entities() when registering them in the protocol, like so: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_tests/src/protocol.rs#L139
the message needs to implement MapEntities
By default, lightyear_avian takes over the sync logic with some opiniated systems here: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_avian/src/plugin.rs#L103
You can disable that and use your own sync logic if you want
indeed there seems to be a problem with the 'spaceships' demo
I identified a bunch of issues that I outlined here: https://github.com/cBournhonesque/lightyear/issues/1117
@pine cape I have a very weird bug occuring with my weapon logic would appreciate some help
In summary: My bullets only move y axis wise
My advice would be to selectively enable trace/debug on some parts
If I add velocity before/with pre spawn it seens it doesnt work.
Or well it works clunkily
After it works okay
[same project] i added a Trigger<OnAdd, LinearVelocity> and Trigger<OnInsert, LinearVelocity> and it spawns fine on the client and server, but when replicating the prespawned entity back to the client, the linear velocity gets locked on Y
client
2025-07-21T16:20:09.708028Z DEBUG psycho_weapons: bullet_origin=Vec3(250.50717, 1.4501282, 1.8548367) bullet_linvel=Vec3(2.5358534, -2.7493591, 9.274183)
2025-07-21T16:20:09.708053Z DEBUG psycho_weapons: Fired bullet on tick 987 on client
2025-07-21T16:20:09.708116Z DEBUG psycho_weapons: event=OnAdd linvel=LinearVelocity(Vec3(2.5358534, -2.7493591, 9.274183)) confirmed=false predicted=false interpolated=false pre_spawned=true
2025-07-21T16:20:09.708194Z DEBUG psycho_weapons: event=OnInsert linvel=LinearVelocity(Vec3(2.5358534, -2.7493591, 9.274183)) confirmed=false predicted=false interpolated=false pre_spawned=true
2025-07-21T16:20:09.819193Z DEBUG psycho_weapons: event=OnAdd linvel=LinearVelocity(Vec3(0.0, -2.7493591, 0.0)) confirmed=false predicted=false interpolated=false pre_spawned=true
2025-07-21T16:20:09.819216Z DEBUG psycho_weapons: event=OnInsert linvel=LinearVelocity(Vec3(0.0, -2.7493591, 0.0)) confirmed=false predicted=false interpolated=false pre_spawned=true
server
2025-07-21T16:20:09.746675Z DEBUG psycho_weapons: Fired bullet on tick 987 on server
2025-07-21T16:20:09.746753Z DEBUG psycho_weapons: event=OnAdd linvel=LinearVelocity(Vec3(2.5358534, -2.7493591, 9.274183)) confirmed=false predicted=false interpolated=false pre_spawned=true
2025-07-21T16:20:09.746827Z DEBUG psycho_weapons: event=OnInsert linvel=LinearVelocity(Vec3(2.5358534, -2.7493591, 9.274183)) confirmed=false predicted=false interpolated=false pre_spawned=true
2025-07-21T16:20:09.708053Z Prespawn on the client
2025-07-21T16:20:09.746675Z Spawn on the server
2025-07-21T16:20:09.819193Z Replication back to the client
You think it's lightyear related? The velocity doesn't seem to be 0 in your logs
Well it zeros on the replication
Can you add trace logs for lightyear_prediction::prespawn to confirm that this happens right after the matching?
I don't have any ideas for why a subset of the struct would be set to 0
you know
sometimes
Mr Peri we make mistakes
hahahaha, lets just all forget about this
/// This makes our walking movement snappier, by making lin_vel zero. If no walking action is giving, it simulated high floor friction
///
/// Increases responsiveness
pub(crate) fn _snappy_walking(
mut query: Query<(Entity, &mut LinearVelocity)>,
walking: Query<&Walking>,
dash: Query<Has<Dash>>,
) {
for (controlled, mut lin_vel) in query.iter_mut() {
// // If not walking and no dash, apply no lin_vel
if walking.get(controlled).is_err() {
let has_dash = dash.get(controlled).unwrap_or_default();
if !has_dash {
lin_vel.0 = Vec3::new(0.0, lin_vel.y, 0.0);
}
}
}
}
This was the cause
@pine cape hot diggity damm mr peri, i have finally got to a point i can see my player mvoing again i must say
looks crisp
Hey, I am trying to set up my own version of the simple_box example and I keep getting this error (both when I run the client or the server):
Encountered an error in system `lightyear_inputs::server::receive_input_message<lightyear_inputs_native::input_message::NativeStateSequence<shooter::protocol::Inputs>>`: Parameter `ServerMultiMessageSender::metadata` failed validation: Resource does not exist
Any idea what I might be doing wrong?
Oh wait I think I am forgetting to actually spawn the Client and Server I guess lol...
Nice, better than before?
yes
Yeah the previous approach probably was buggy. Now it's smooth even with constant rollbacks
I'm trying to test the cb/fix-spaceship-demo branch but I'm getting this error:
Updating git repository `https://github.com/cBournhonesque/lightyear.git`
error: no matching package named `lightyear_avian2d` found
location searched: Git repository https://github.com/cBournhonesque/lightyear.git?rev=9c41ad2847193ac0fb265e079d0c2cc3f3c2dc86
required by package `lightyear v0.22.4 (https://github.com/cBournhonesque/lightyear.git?rev=9c41ad2847193ac0fb265e079d0c2cc3f3c2dc86#9c41ad28)`
... which satisfies git dependency `lightyear` of package `deep_field v0.0.1-beta (/Users/rherv/deep_field_beta/workspace/deep_field)`
This is what my Cargo.toml looks like:
lightyear = { git = "https://github.com/cBournhonesque/lightyear.git", rev="9c41ad2847193ac0fb265e079d0c2cc3f3c2dc86", default-features = false, features = [ "interpolation", "prediction", "replication", "leafwing", "frame_interpolation", "netcode", "udp", "input_native" ] }
This might be due to the newly commented path = "../lightyear_avian/src/lib.rs" in lightyear_avian2d/Cargo.toml though I'm not sure.
It is, you would need to uncomment it.
I commented it because for some reason it was blocking me from releasing
I still have to think of the correct solution. I think keeping the run_if(is_in_rollback) might be the simplest to avoid spawning extra bullets.
@stray sinew I updated the example, it should work now.
The main change is to:
- run the spawn_bullet system even during rollbacks (the bullets are spawned predictably from a certain input; so the PreSpawned bullets are despawned at the start of rollbacks and re-spawned during rollbacks when we replay the Fire input)
- do not run the spawn_bullet system for remote clients. You could, but then since you receive the 'Release' input later than it's actually pressed, you would spawn PreSpawned bullets that don't actually exist. They get despawned quickly but it is distracting
@pine cape BEI Trigger<Completed<A>> being triggered every frame on server, did i miss a configuration?
Hm it might be a bug? Probably because I keep predicting that the Action stays the same for future ticks
@pine cape trying to make a small repro for that but somehow i'm missing something to allow replication, i never started a project with lightyear from scratch, just joined one that was already on going, so i must be missing something minimal, either that or the setup that i'm doing is just too weird
basicaly i'm trying to create a server and a client using std::thread::scope
i can start the server and connect a client to it, but i must be missing something to replicate things from one to another
thanks
I ran your tests, I only get one 'Completed' log on the server
using the main branch
#[derive(InputContext, Reflect)]
#[input_context(schedule = FixedPreUpdate)] // Completed gets spammed without this
struct TestInputs;
The schedule has to be a Fixed schedule. Are you saying it doesn't work with FixedUpdate?
#[derive(InputContext, Reflect)]
// #[input_context(schedule = FixedPreUpdate)] // Completed gets spammed without this
struct TestInputs;
this was my code before creating the repro
Inputs need to be scheduled in FixedUpdate to work properly, since only FixedUpdate is synced between client and server
i've already hit that many times and still continue to do so
cant this be something like <C: InputContext<Schedule = FixedPreUpdate>>?
https://github.com/cBournhonesque/lightyear/blob/f96a94090b90b1aa64c87a2415b617bb8e045b4b/lightyear_inputs_bei/src/plugin.rs#L30
Even with the schedule specified?
Sure, can you make a PR?
i've already put the input systems in Update many times and still continue to put them on Update even though i know it breaks things
DOne
Thanks! it's working flawlessly now
Awesome! The bullets are spawned at a distance because of the latency, but you can add some input delay to correct that
i added the spawning at a distance myself, without the distance it works fine and spawns exactly on the player
I was wondering, are there mechanisms to implement LOD to minimize data transfer for objects far away ?
Or conditionally
Yes you can set a replication frequency on each ReplicationGroup (a ReplicationGroup is a list of entities replicated together, by default every entity is in its own ReplicationGroup)
If you are bandwidth limited, you can also set a priority per ReplicationGroup, and updates will be sent in descending order of priority
Hey Periwink, I found a possible bug for steam connections, I am getting the error:
thread 'main' panicked at C:\Users\Elizabeth\.cargo\git\checkouts\lightyear-16a1ca81dacb5ed5\f96a940\lightyear_connection\src\client.rs:70:14:
A Connected entity must always have a RemoteId component
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic when applying buffers for system `lightyear_Menu_Example::networking::client::client_connect`!
I fix it by added the RemoteId(Steam(steam_id.raw())) component before triggering connect and that connects with a few errors on the server (but nothing crashes).
Is manually adding a RemoteId component how to should work or is this a bug?
At the top of my head I would say it's expected, but it is also possible that the RemoteId should be added as part of lightyear_steam and there is some ordering issue. Could you please open an issue so that I can take a look next week?
yep:)
~~Are replication groups connection specific? ~~ (I'll go read the manual)
couldn't be me lol, Im worse than Phineas and Ferb... "Where's Peri??"
LOL
Good question, I don't it is. It probably should be
I was thinking of optimizing transactions between server clients while obfuscating or diminishing data they should not see or barely see.
Example. If I create a destroyer versus submarine game I won't send the submarine data until they destroyer has detected it on the sonar. (not what I'm doing but it's analogous)
When can an entity with Client be safely despawned after triggering disconnection? It seems that even despawning based on Added<Disconnected> does not properly disconnect the client, so the server disconnects the client due to a timeout:
2025-07-24T01:07:22.182102Z ERROR lightyear_udp::server: Error receiving UDP packet: Une connexion existante a dรป รชtre fermรฉe par lโhรดte distant. (os error 10054)
[...]
2025-07-24T01:07:22.630537Z ERROR lightyear_udp::server: Error receiving UDP packet: Une connexion existante a dรป รชtre fermรฉe par lโhรดte distant. (os error 10054)
2025-07-24T01:07:22.726815Z INFO lightyear_netcode::server_plugin: Disconnection from netcode client 4297587319730296768. Despawning entity.
That's possible via the NetworkVisibility component
If you triggered Disconnect on the client entity, it starts by sending a bunch of Disconnection packets to the server. So the netcode server is aware that the client has requested disconnection.
This doesn't do anything to the underlying IO, you can trigger Unlink to close the underlying io (here UDP).
I'm not sure that in your case the disconnection on the server was due to a timeout
Oh I just got steam lobbies working, with the ability to request people to join your game, I think I'm ready to actually work on the porting over the game part now lol
currently looking for how you setup visual_interpolation, because of this error
Encountered an error in system `lightyear_frame_interpolation::visual_interpolation<bevy_transform::components::transform::Transform>`: Parameter `Res<InterpolationRegistry>` failed validation: Resource does not exist
commenting out app.add_plugins(FrameInterpolationPlugin::<Transform>::default()); seems to let everything else work (Copying your spaceship demo for now)
What does your Transform interpolation look like?
what do you mean by this?
I remember getting that same error if I didn't initialize the visual correction for the component or forgot to run this system:
fn add_visual_interpolation_components<T: Component>(
trigger: Trigger<OnAdd, T>,
query: Query<Entity, With<Predicted>>,
mut commands: Commands,
) {
if !query.contains(trigger.target()) {
return;
}
commands
.entity(trigger.target())
.insert((FrameInterpolate::<T>::default(),));
}
fn add_visual_interpolation_components(
// We use Position because it's added by avian later, and when it's added
// we know that Predicted is already present on the entity
trigger: Trigger<OnAdd, Position>,
q: Query<Entity, (Without<Wall>, With<Predicted>)>,
mut commands: Commands,
) {
if !q.contains(trigger.target()) {
return;
}
debug!("Adding visual interp component to {:?}", trigger.target());
commands
.entity(trigger.target())
.insert(FrameInterpolate::<Transform> {
// We must trigger change detection on visual interpolation
// to make sure that child entities (sprites, meshes, text)
// are also interpolated
trigger_change_detection: true,
..default()
});
}
its copied exactly from the spaceship example, what do you mean by visual correction initialization?
well the game part of it is, there could be issues with my separate mode menu stuff, the codes here if that helps https://github.com/SueHeir/lightyear-menu
I might be wrong but try registering Transform here https://github.com/SueHeir/lightyear-menu/blob/main/src/networking/protocol.rs
ok:)
adding app.register_component::<Transform>(); does not change the crash, is this what you meant?
maybe it need linear interpolation added?
try add_correction_fn and interpolation
yeah I can't add correction_fn unless i impliment a LerpFn<Transform>
After doing some digging you shouldn't have to its registered in avian's plugin here: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_avian/src/plugin.rs
// do not replicate Transform but make sure to register an interpolation function
// for it so that we can do visual interpolation
// (another option would be to replicate transform and not use Position/Rotation at all)
app.world_mut()
.resource_mut::<InterpolationRegistry>()
.set_interpolation::<Transform>(TransformLinearInterpolation::lerp);
app.world_mut()
.resource_mut::<InterpolationRegistry>()
.set_interpolation_mode::<Transform>(InterpolationMode::None);
For whatever reason this is not running.
ok thats good! it's probably something on my end, i'll keep looking around
Maybe you didn't enable the avian feature? Or you can copy-paste the code above about registering an interpolation function for Transform
@pine cape my PR immediatly went deprecated
bei 0.15 completely removes InputContext
@pine cape do you know the condition for this error? lightyear_replication::send::sender: Received an update message-id ack but we don't know the corresponding group id
i am doing work related to bullets so i just spam the shoot button to see the spawning of the bullets, and after some time this gets blasted on the server console
@pine cape when creating multiple entities with Prespawned on the same frame, the salt needs to be distinct, right?
yes
I think it's possible that this happens if your message is split between multiple packets, and one of them gets dropped
Maybe you have many predicted entities so the message is too big to fit in one packet?
hm i think this will always get triggered actually even if the packet doesn't get dropped, but if the the packet is split into fragments
enable avian2d in the toml right?
Yep
it was already enabled:(
So i had to import lightyear_frame_interpolation separately, is that normal?
I would just try to do things like in the example
Maybe the frame_interpolate feature needs to be enabled
@pine cape what do you suggest for pseudo-random generator that is synced between server and client?
when prespawning bullet they have a bit of a spread, so i would need to have a pseudorng that is the same on client and server to generate the spread
I think using bevy_rand you can set a seed per entity, just make sure the seed is the same on client and server. Maybe tick + transform? Or the Prespawn hash
This is what I do. shooting_index is just the total amount of bullets the player has fired. It's added later because my PeerIDs are sequential starting from 1 which would cause collisions.
let shooting_index = weapon_manager.shooting_index;
weapon_manager.increment_shooting_index();
let id = character_marker.0.to_bits();
let mut rng = rand::prelude::SmallRng::seed_from_u64(id);
let seed rng.random_range(1_000_000..9_999_999) + shooting_index as u64;
I made an immutable component that is replicated that contains the seed, and on insert of that component, I add a component with the actual rng from that seed
@pine cape Will PredictionMode::Full ever be supported for immutable components? I want to have ChildOf be copied to the predicted and was wondering whats the best way to do this
PredictionMode::Once will make your ChildOf be copied to the Predicted
do you mean that you need the ChildOf component to be rolled back?
I think supporting PredictionMode::Full on immutable components is do-able, it just needs some work
Are you changing the immutable component that much for it to need resync every tick? (That is how I understand Full)
lightyear does not have a frame_interpolate feature:(
I'm probably just going to leave frame interpolation off for now and just ignore why thats not working.
I currently have a more important bug to worry about, and that is steam, does anyone a working steam example they've tested with two steam accounts?
is it currently possible to sync Transform->Position with lightyear's avian support
specifically, I have a bug with some unrelated code which I believe has stopped working because lightyear's replacement of the stock avian SyncPlugin doesn't sync changes to transform back into position
Excited to see the new deterministic input stuff!
Also @pine cape I just upgraded to 0.22.5 and am seeing INFO logs being spammed, opened a ticket, I dont think they should spam by default
I've identified a race condition with a closure related to steam: https://github.com/cBournhonesque/lightyear/blob/f9ad6ff3bf722860f469b4bdbc1d0a35d44baaa5/lightyear_steam/src/client.rs#L70-L82
2025-07-26T22:21:30.163607Z DEBUG lightyear_steam::client: SteamClientPlugin: LinkStart added
2025-07-26T22:21:30.163746Z DEBUG lightyear_steam::client: SteamClientPlugin: query has steamclientio
2025-07-26T22:21:30.163877Z DEBUG lightyear_steam::client: SteamClientPlugin: Linked Added, triggering connected
thread 'main' panicked at C:\Users\Elizabeth\.cargo\git\checkouts\lightyear-314564b20840a4cf\3681590\lightyear_connection\src\client.rs:70:14:
A Connected entity must always have a RemoteId component
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic when applying buffers for system `lightyear_Menu_Example::networking::client::client_connect`!
Encountered a panic in system `Pipe(bevy_state::state::transitions::last_transition<lightyear_Menu_Example::MultiplayerState>, bevy_state::state::transitions::run_enter<lightyear_Menu_Example::MultiplayerState>)`!
2025-07-26T22:21:30.164757Z DEBUG lightyear_steam::client: SteamClientPlugin: closure started
2025-07-26T22:21:30.164884Z DEBUG lightyear_steam::client: SteamClientPlugin: remote Id Added
2025-07-26T22:21:30.165139Z TRACE lightyear_steam::client: Starting LinkStart for SteamClientIo on entity 136v3#12884902024. Spawning aeronet entity: 137v3#12884902025
host-client doesn't work with the deterministic feature because it tries to add the ChecksumMessage twice in the common crate. Once for the client and once for the server. The registery utils panics when adding the same message. I see 2 possible fixes:
- don't panic in utils and ignore the duplicate add
- Update
add_message_custom_serdein message registry to check if the type is already registered and ignore the add if it already exists
Thoughts?
I think we can ignore the duplicate add
I see, thanks. So the issue is that RemoteId is added in the future because it's a Command. Will look into how this can be solved
Thanks, yes this should be a trace log
I think for custom cases you should disable the 'avian feature of lightyear and add the lightyear_avian plugin directly, which let's you control some parameters related to sync
hi @pine cape - I'm using Lightyear 0.21, and getting a problem with Leafwing ActionStates sync'ing between client and server.
The key "pressed" ActionStates syncs just fine, but the values I get for the "current_duration" of the ActionStates on the server side are very small numbers - basically 0.0. I can't see any calls to ActionState::current_duration() in the Lightyear examples or tests, and see some comments/TODOs in the code that suggest it may not be implemented.
What do you recommend? I think I should be switching my input handling from Leafwing to BEI anyway - will key press durations sync with that, i.e. using lightyear_inputs_bei? (The game is very simple, and switching to BEI would be straightforward.)
I'm not sure I follow your logs; do you have a branch with the logs added?
My understanding is:
-
you trigger Connect
-
LinkStarts queues a command that adds RemoteId, spawns the aeronet entity and adds SessionEndpoint/Connecting on the aeronet entity
-
then you get an error because on
Connectedadded, lightyear_connection expects a RemoteId. That means thatConnectedis added before the LinkStart command completes. However I don't get why/whereConnectedis added, normally it should be added by aeronet_steam -
there is another observer in
lightyear_steamthat addsConnectedwhenLinkedis added; so we have to find whereLinkedis added
Hey, I would recommend moving to BEI! Key press durations will work fine with BEI
I think the issue comes from: https://github.com/cBournhonesque/lightyear/blob/98acfd778ea2d0664df0d6086d5c8a97763b0eaf/lightyear_inputs_leafwing/src/input_message.rs#L24-L24 and https://github.com/cBournhonesque/lightyear/blob/98acfd778ea2d0664df0d6086d5c8a97763b0eaf/lightyear_inputs_leafwing/src/input_message.rs#L79-L79
Normally i would have to provide the tick duration there for the durations inside ActionState to be correct, but currently I don't pass that as context
Thanks for the quick reply! Yes, that was the comment I saw in the code. I'll move to BEI.
Cool. Iโll open a PR later today
@pine cape My brother mentioned to you an issue we had with childing avian Colliders and them not moving with the parent.
I think this section of the code is maybe a bug? https://github.com/cBournhonesque/lightyear/blob/98acfd778ea2d0664df0d6086d5c8a97763b0eaf/lightyear_avian/src/sync.rs#L91-L110
The reason I say so is it breaks the default Bevy child transform propagation behavior; Also seems to be a regression from pre 0.21
What is the logic behind needing to update the childs Transform? In general a child will have a fixed transform and inherit global from the parent (plus its own local to parent transform). This block was doing the unwinding of the transform stack for children. I enabled the manual sync and ran this myself just deleting the child section here and it all works as expected. I am curious as to the reasons as why children need updated transforms.
I am now using just (and it all works as expected); Along with enabling transform_to_position from Avians SyncConfig
pub fn position_to_transform(mut query: Query<PosToTransformComponents, PosToTransformFilter>) {
for (mut transform, pos, rot, parent) in &mut query {
if parent.is_none() {
transform.translation = pos.f32();
transform.rotation = rot.f32();
}
}
}
I just copied the code from avian: https://github.com/Jondolf/avian/blob/v0.3.1/src/sync/mod.rs#L311
but it's also applied to entities that don't have RigidBody
although it looks like it was recently updated: https://github.com/Jondolf/avian/pull/785
Ahh yes, so that makes sense for Avian, since childed rigid bodies don't inherit their parents transforms per the Avian docs. But for lightyear it's odd, maybe a more complex query is needed?
I'm not sure the correct answer here, but it was definitly breaking sensor collider children which is not great
Your brother's issue is https://github.com/cBournhonesque/lightyear/issues/1128#issuecomment-3124508269 ?
Not his ticket, but yes looks like the same issue.
TBH after reading your message I still don't really understand what the issue is
I think the reason why the sync needs to udpate the child's Transform is that the Position component is the global position, but the Transform position is a local position. So to compute the correct local Transform we need to take into account the parent's transform as well
yes, just import it separately like in https://github.com/cBournhonesque/lightyear/blob/05873a299b479ac78f7d8e789262673c22d78ca1/examples/avian_physics/src/renderer.rs#L30-L30
I just fixed it
Yeah I added some print outs and tried to fix it in a fork but no success. My poor solution was to trigger connected when remoteId was added, this keeps it from crashing but then a lot of server errors happen and nothing is replicated
My game requires an entity's parent be changed regularly. Currently when the server changes an entity's parent, it does not get copied to the predicted after the first time. This is my current crappy work around:
pub fn child_of_to_predicted(
q_confirmed_character: Query<
(&Confirmed, &ChildOf),
(Changed<ChildOf>, With<Confirmed>, Without<Predicted>),
>,
prediction_manager: Single<&PredictionManager, Without<ClientOf>>,
mut commands: Commands,
) {
let entity_map = unsafe { &*prediction_manager.predicted_entity_map.get() };
for (confirmed, confirmed_child_of) in q_confirmed_character.iter() {
let Some(predicted_entity) = confirmed.predicted else {
continue;
};
let Some(predicted_parent) = entity_map.confirmed_to_predicted.get(&confirmed_child_of.0)
else {
continue;
};
commands
.entity(predicted_entity)
.insert(ChildOf(*predicted_parent));
}
}
You can use PredictionMode::Simple for updates to be synced to the predicted even after the first time.
The component still won't get checked for rollbacks though
Did not know that, thanks!
This is probably why you had the panic for duplicate message type registration
```called Result::unwrap() on an Err value: DuplicateRegistration(ComponentId(265), ComponentId(339))
stack backtrace:
0: __rustc::rust_begin_unwind
at /rustc/6b00bc3880198600130e1cf62b8f8a93494488cc/library/std/src/panicking.rs:697:5
1: core::panicking::panic_fmt
at /rustc/6b00bc3880198600130e1cf62b8f8a93494488cc/library/core/src/panicking.rs:75:14
2: core::result::unwrap_failed
at /rustc/6b00bc3880198600130e1cf62b8f8a93494488cc/library/core/src/result.rs:1732:5
3: core::result::Result<T,E>::unwrap
at /home/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs:1137:23
4: bevy_ecs::world::World::register_required_components
at /home/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/world/mod.rs:356:9
5: bevy_app::app::App::register_required_components
at /home/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_app-0.16.1/src/app.rs:838:26
6: lightyear_messages::client::<impl lightyear_messages::registry::MessageRegistration<M>>::add_client_direction
at /home/.cargo/git/checkouts/lightyear-16a1ca81dacb5ed5/8f22a63/lightyear_messages/src/client.rs:14:17
7: lightyear_messages::registry::MessageRegistration<M>::add_direction
at /home/.cargo/git/checkouts/lightyear-16a1ca81dacb5ed5/8f22a63/lightyear_messages/src/registry.rs:329:9
8: <lightyear_deterministic_replication::checksum::ChecksumReceivePlugin as bevy_app::plugin::Plugin>::build
at /home/.cargo/git/checkouts/lightyear-16a1ca81dacb5ed5/8f22a63/lightyear_deterministic_replication/src/checksum.rs:216:14
We finally got part of my game working again<3, I know its been doom and gloom bug reporting from me for the last few weeks, but this is exciting!
nice, looks awesome! This is with other client inputs being predicted, on top of state replication?
I havenโt touched other clients yet lol, but thatโs the plan!
2025-07-28T17:52:44.538985Z INFO corn_game::systems::network: starting client
2025-07-28T17:52:44.539925Z INFO lightyear_netcode::client: client connecting to server 127.0.0.1:42217 [1/1]
2025-07-28T17:52:44.539960Z WARN lightyear_webtransport::client: Connecting with no certificate validation
2025-07-28T17:52:44.909626Z INFO lightyear_netcode::client_plugin: Client Netcode(0) connected
2025-07-28T17:52:44.910112Z ERROR lightyear_serde::entity_map: Failed to map entity 209v1#4294967505
2025-07-28T17:52:44.910229Z INFO corn_game::ecs::test_cube: spawning test cube
thread 'main' panicked at /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/avian3d-0.3.1/src/collision/collider/backend.rs:133:31:
Entity PLACEHOLDER does not exist (enable `track_location` feature for more details)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `lightyear_replication::receive::ReplicationReceivePlugin::apply_world`!
well this is a riddle
anyone got something like this
I'm also running into some strange behavior with the only smoke signal in the logs being the same Failed to map entity error.
This is while running a host-client in one window and a client that connects to it in another. Another oddity is that I don't have two copies of the relevant replicated components on the client (I would expect one Confirmed copy and one Interpolated or Predicted copy). Instead there is just one copy without Confirmed but with ShouldBePredicted or ShouldBeInterpolated
What would prevent replication from reaching a Confirmed state for these entities? I'm still looking through lightyear for some clues but I feel a bit lost for ideas.
edit: I'm on lightyear 0.22.5
which example shows replication of relationships?
I think one way or another the behavior here is not ideal, in addition to whatever the actual issue is.
Either avian should not panic when parent is PLACEHOLDER or lightyear should not set PLACEHOLDER when entity mapping fails
(assuming I'm correct that that is what's happening)
I think probably, replication messages should be discarded if mapping fails.
this is the same situation In which I get the error, although I'm also using client replication
I don't get it with a simple test cube scene though
and the entity ID in my log is one which isn't replicated :/
probably I'm replicating hierarchy (idk how that works in lightyear now) and have a replicated item as a child
yeah
that's what it was
:/ I don't know how that should work, but I know it shouldn't panic
In my case, the "failed to map entity" turned out to be a red herring that was not related to why Confirm wasn't being added.
I thought that replication and interpolation should be working because host-client by itself was working.
The missing piece was to add these two to the client entity:
PredictionManager::default(),
InterpolationManager::default(),
Host-client seems to work fine without these present, but remote clients do not.
The only hint I found to do so was its presence in the common client.rs in examples, not in the client.rs of any of the examples that contained PredictionTarget and InterpolationTarget in their server.rs. These nuggets of info being stashed away in the shared example code (examples/common) has tripped me up several times on this upgrade.
does anyone know how hierarchy replication is managed now
I more or less just want to turn it off. and not replicate ChildOf at all
When the DisableReplicateHierarchy marker component is added to an entity, we will stop replicating their children.
I think what I need is to disable replicating the ChildOf component
since the entity's parent is not replicated.
yeah, that doesn't seem to do it, it is still trying to replicate ChildOf
I don't think there is one; this is close: https://github.com/cBournhonesque/lightyear/blob/main/examples/replication_groups/src/protocol.rs#L151
it was created before relationships were a thing
HostServer mode is a bit brittle in the sense that it tries to emulate the normal replication behaviour by adding fake components (Predicted, Interpolated, etc.) that the client usually queries on: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_replication/src/host.rs#L62-L62
Confirmed is added here: https://github.com/cBournhonesque/lightyear/blob/5dc3dc3e17a8b821c35162b904b73eea0e1c69be/lightyear_prediction/src/spawn.rs#L17
- is there only one entity with
ReplicationReceiverLocalTimelineandPredictionManager? You might have multiple - try adding debug logs for
lightyear_prediction::spawn
Oh i see you solved the issue. Yeah the HostClient doesn't need these because it doesn't actually do Prediction, since the host-client is on the same timeline as the server.
I'd love a PR that helps simplify the examples! I would still need a way to run the examples in all the different modes (host-server, etc.) since that's helpful to test various functionalities. But i'd be fine with a 'default' version where most of the code is inside the example folder itself, since it has been a repeated feedback that the shared.rs folder is hard to grok
What are you trying to do exactly?
You have
- P1 (non-replicated parent)
- C1 (replicated child)
and you want to not replicate ChildOf, since the entity mapping failure (entity mapping fails because the parent is not replicated) causes a panic on the remote?
- C1 (replicated child)
You want to keep ChildOf in your protocol, because some other entities might want to replicate it?
You can add the ComponentReplicationOverrides::<ChildOf>::disable_all() component that will disable sending ChildOf on all ReplicationSenders.
https://docs.rs/lightyear/latest/lightyear/prelude/struct.ComponentReplicationOverrides.html
API documentation for the Rust ComponentReplicationOverrides struct in crate lightyear.
That would work, but id prefer to not have ChildOf even registered for replication
Yeah, part of this is just the vegetables I have to eat when using a cutting edge major refactor release of a library where the dust around documentation hasn't settled yet. Once more of that dust settles and I have more confidence in what I'm doing in lightyear, I wouldn't mind pitching in on improving examples/documentation. Right now I just think I'd break more things than I'd fix.
I register it by default; but you can disable it with
app.register_component::<ChildOf>()
.with_replication_config(ComponentReplicationConfig { disable: True, ..default() })
https://docs.rs/lightyear/latest/lightyear/prelude/struct.ComponentRegistration.html#method.with_replication_config
https://docs.rs/lightyear/latest/lightyear/prelude/struct.ComponentReplicationConfig.html#structfield.disable
API documentation for the Rust ComponentReplicationConfig struct in crate lightyear.
API documentation for the Rust ComponentRegistration struct in crate lightyear.
I will say though, the refactor is dope, and I like the new approach overall now that I've had enough time to get a decent grasp on how it works. Networked events (RemoteTriggers) are sick, and the shift to using more components for everything has had a lot of cool positive side effects in our project. We're also hyped about the BEI stuff as well.
Thanks! I'm really happy with the move to heavily rely on components/observers, + the fact that the code base is relatively client/server agnostic.
Some things are still tricky, like the lack of observer ordering.
I also had to roll out my many-to-many relationships for Replicate (since an entity can be replicated via multiple senders); but hopefully bevy will introduce a better version than mine.
@pine cape follow up fix for host-client mode to ignore duplicate registrations for required components for messages https://github.com/cBournhonesque/lightyear/pull/1135
I wonder what the right way of going about building a demo web-build is.
I got everything building for wasm and running in browser single player. And networking is working native in host-client mode
I could build a dedicated server (might want a inprocess headless server in the end regardless.)
but I think a thin network proxy would be better
then just, whoever opens the website first can be host-client and I don't need server build
but
- I think I'd have to modify or extend lightyear to support having a server in wasm
- I'd have to figure out how to setup the proxy.
Registering ChecksumMessage twice in host-client mode is now problematic because the message type registry (kind_map) seems to think it's 2 different types
Encountered an error in observer lightyear::protocol::ProtocolCheckPlugin::receive_verify_protocol: the message protocol doesn't match
I found the issue. It's because the hasher for the message registry was being modified by adding it a second time. Even with this fix, there's still some errors for host-client mode seemingly on the host-client server. Lots of checksums mismatch but I think it's because the host-client server isn't running the simulation for some reason https://github.com/cBournhonesque/lightyear/pull/1136
Some options would be:
- running a dedicated server
- using a managed service like Edgegap to spawn a server pod whenever clients are connected
- add matchbox as a transport layer (which shouldn't be too hard)