#lightyear
14521 messages · Page 15 of 15 (latest)
Thanks for your insight, that’s an interesting organization. It seems that you prefer conditional compilation with feature flags so that client-specific, server-specific, and shared code can live side by side.
I have two questions for you:
- What about
src/core/physics.rs? Does it contain anything that is specific to the client or the server? - What do you put in
src/core/protocol.rs?
I find it difficult to clearly define what the protocol really is when using lightyear. As I understand it, the protocol is all the structured information communicated between clients and the server. So what lightyear calls Messages are obviously part of it, but technically, replicated components are part of it too.
I imagine that for a large project like Computronium, protocol.rs doesn't actually contains every replicated components. Most of the time these components are not just "data to be replicated" since they are also part of the client/server logic.
Ok I'm a little stuck. I have created the Client on the client, the NetcodeServer on the server, added MessageSender on the server and MessageReceiver on the client, registered my message, and created a channel for it. Yet my message is not being received on the client...
Client spawner:
let auth = Authentication::Manual {
server_addr: config.ip,
client_id: 0,
private_key: Key::default(),
protocol_id: 0,
};
let client_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0);
let client = commands
.spawn((
Client::default(),
LocalAddr(client_addr),
PeerAddr(config.ip),
Link::new(None),
ReplicationReceiver::default(),
NetcodeClient::new(auth, client::NetcodeConfig::default()).unwrap(),
UdpIo::default(),
MessageReceiver::<ServerGreeting>::default(),
MessageSender::<ClientReady>::default(),
))
.id();
commands.trigger(Connect { entity: client });
Server spawner:
let server = commands
.spawn((
NetcodeServer::new(server::NetcodeConfig::default()),
LocalAddr(SocketAddr::new(
IpAddr::V4(Ipv4Addr::UNSPECIFIED),
config.port,
)),
ServerUdpIo::default(),
))
.id();
New client handler:
fn handle_new_client(event: On<Add, Connected>, mut commands: Commands) {
commands.entity(event.entity).insert((
MessageSender::<ServerGreeting>::default(),
MessageReceiver::<ClientReady>::default(),
));
let entity = event.entity;
commands.run_system_cached_with(send_greeting, entity);
}
Is there some other step I'm missing?
I can see that my client is connecting, and I added a log that it's hitting the send.
I am seeing in my logs that I am sending my message before the connection is "confirmed". So maybe sending messages in On<Add, Connected> isn't allowed?
Nope looks like that's not it. I delayed the message by 1s (after confirmation) and it still wasn't sending the message...
Did you put the right direction when registering the message?
I think you've already checked, but you never know..
I don't know if there is something weird on my end but I don't have to insert MessageSender and MessageReceiver components
they are already on the entities
Yup! the channel directions look correct, as well as the message directions
Oh hmmm let me try removing those then
Oh interesting, yeah I can remove the inserts as well looks like. Still not receiving the message though :/
Im not sure, when there is more than 100+ zombies, remote server lags, but in local everything is fine
AGHHHH Turns out the problem was I was putting my message listening systems in PreUpdate. Running them in Update fixes things. Maybe also just configuring them after the receive systems will be enough
Frustrating
Maybe MessageReceiver::clear_typed should add a log message that messages are being dropped?
- What about
src/core/physics.rs? Does it contain anything that is specific to the client or the server?
Yes, but only because we have a custom picking system to do physics-based "sub-picking", otherwise it's just shared setup, and done in core to load everything ahead of any gameplay systems.
This is physics.rs
use crate::prelude::*;
use bevy_tnua::prelude::*;
use bevy_tnua_avian3d::*;
pub(super) fn plugin(app: &mut App) {
app.insert_resource(avian3d::physics_transform::PhysicsTransformConfig {
transform_to_position: false,
position_to_transform: true,
..default()
});
app.add_plugins(PhysicsPlugins::default().build()
// disable the position<>transform sync plugins as it is handled by lightyear_avian
.disable::<PhysicsTransformPlugin>()
.disable::<PhysicsInterpolationPlugin>()
.disable::<IslandPlugin>()
.disable::<IslandSleepingPlugin>(),
);
// Tnua will run in rollback
app.add_plugins(TnuaControllerPlugin::new(FixedUpdate));
app.add_plugins(TnuaAvian3dPlugin::new(FixedUpdate));
}
#[cfg(feature = "client")]
pub(super) mod client_only {
use avian3d::math::*;
use bevy::{picking::{backend::{ray::RayMap, HitData, PointerHits}, PickingSystems}};
use super::*;
pub fn plugin(app: &mut App) {
// Cameras also need to be marked PhysicsPickable
app.insert_resource(PhysicsPickingSettings {
require_markers: true,
});
// Custom physics picking system, reimplements PhysicsPickingPlugin from Avian
app.init_resource::<PhysicsPickingSettings>();
app.add_systems(PreUpdate, physics_picking_system.in_set(PickingSystems::Backend));
}
fn physics_picking_system(...) { ... }
}
- What do you put in src/core/protocol.rs?
Add any plugins, register components, and I do some specific stuff for our mod synchronization systems using rooms. Pretty much anything lightyear related.
Here's a stripped-down version:
use crate::prelude::*;
use std::time::Duration;
use lightyear::{
...
};
pub(super) fn plugin(app: &mut App) {
let tick_duration = Duration::from_secs_f64(1.0 / 64.0);
// Server and Client plugins (must be first)
app.add_plugins(server::ServerPlugins{ tick_duration });
#[cfg(feature = "client")]
app.add_plugins(client::ClientPlugins{ tick_duration });
app.add_plugins(RoomPlugin);
app.add_plugins(InputPlugin::<NetworkInputComponent> {
config: InputConfig {
rebroadcast_inputs: true,
..default()
},
});
app.add_plugins(LightyearAvianPlugin {
replication_mode: AvianReplicationMode::PositionButInterpolateTransform,
..default()
});
app.add_plugins(HierarchySendPlugin::<SomeRelation>::default());
// Actions
app.register_input_action::<SomeAction>();
// don't know if this is still necessary, or a remnant from older versions
app.register_type::<Actions<NetworkInputComponent>>();
app.register_type::<ActionOf<NetworkInputComponent>>();
// Channels
app.add_channel::<UnorderedReliableChannel>(
ChannelSettings {
mode: ChannelMode::UnorderedReliable(ReliableSettings::default()),
..default()
}
).add_direction(NetworkDirection::Bidirectional);
// Events
app.register_event::<SomeEvent>()
.add_direction(NetworkDirection::ClientToServer)
.add_map_entities();
// Components
app.register_component::<SomeComponent>();
// These are the settings that I've found best for my use of Tnua
app.register_component::<Position>()
.add_prediction()
.add_should_rollback(position_should_rollback)
.register_linear_interpolation()
.add_linear_correction_fn()
.enable_correction();
app.register_component::<Rotation>()
.add_prediction()
.add_should_rollback(rotation_should_rollback)
.register_linear_interpolation()
.add_linear_correction_fn()
.enable_correction();
app.register_component::<LinearVelocity>()
.add_prediction()
.add_should_rollback(linear_velocity_should_rollback);
app.register_component::<AngularVelocity>()
.add_prediction()
.add_should_rollback(angular_velocity_should_rollback);
}
fn position_should_rollback(this: &Position, that: &Position) -> bool {
(this.0 - that.0).length() >= 0.1
}
fn rotation_should_rollback(this: &Rotation, that: &Rotation) -> bool {
this.angle_between(*that) >= 0.1
}
fn linear_velocity_should_rollback(_this: &LinearVelocity, _that: &LinearVelocity) -> bool {
false
}
fn angular_velocity_should_rollback(_this: &AngularVelocity, _that: &AngularVelocity) -> bool {
false
}
// there's also some modding specific workflow using lightyear rooms here, omitted for brevity
#[cfg(feature = "client")]
pub(super) mod client_only {
use super::*;
// I had some issues with my configuration of frame interpolation
// and is commented out in my current version
// so your mileage may vary
use lightyear_frame_interpolation::FrameInterpolationPlugin;
pub fn plugin(app: &mut App) {
app.add_plugins(FrameInterpolationPlugin::<Transform>::default());
}
}
Most of the time these components are not just "data to be replicated" since they are also part of the client/server logic.
I'm handling this distinction using conditional compilation. I only register and replicate components that need to be replicated. I use conditional compilation to handle anything else - including changing required components based on client/server distinction.
You can even get really crazy with cfg_attr and have multiple required component definitions that will be merged.
#[derive(Component, Debug, Reflect, Serialize, Deserialize, Clone, PartialEq)]
#[reflect(Component)]
#[cfg_attr(feature = "dev", require(
Name::new("Avatar"),
))]
#[cfg_attr(feature = "client", require(
Visibility::default(),
bevy_tnua::TnuaAnimatingState::<PlayerAnimationStates>::default(),
))]
#[require(
Position::new(get_random_vec3(Vec3::new(-5.0, 10.0, -5.0), Vec3::new(5.0, 10.0, 5.0))),
RigidBody::Dynamic,
Collider::capsule(0.3, 0.8),
LockedAxes::ROTATION_LOCKED,
Transform,
SleepingDisabled,
BlockingComponent,
WallBlockingComponent,
TnuaController,
TnuaAvian3dSensorShape(Collider::sphere(0.1)),
InputAccumulationComponent,
)]
pub struct AvatarComponent {
pub avatar_color: Color,
}
Wow! Thank you for taking the time to answer me.
In my case, I think I will try to further split protocol.rs. Am I right in thinking that SomeComponent which is registered in protocol.rs is not defined there?
I really like this use of cfg_attr, I'm going to kindly steal this architecture :p
Correct, all components are defined in src/data/components.rs. This isn't a permanent rule for us though. We may split the file into multiple inside of the src/data directory. It will be loaded the same way though.
The crate prelude in src/lib.rs manages visiblity for all the stuff in data so every component doesn't have to be specified as a use ... in all the systems.
in src/lib.rs
extern crate self as computronium;
use bevy::prelude::*;
mod core;
mod data;
mod systems;
...
pub(crate) mod prelude {
pub use super::data::exports::*;
pub use avian3d::prelude::*;
pub use bevy::prelude::*;
pub use lightyear::input::bei::prelude::*;
pub use lightyear::prelude::*;
}
src/data/mod.rs
use crate::prelude::*;
mod actions;
mod components;
mod events;
mod states;
pub mod exports {
pub use super::{
actions::*,
components::*,
events::*,
states::*,
};
}
pub(super) fn plugin(app: &mut App) {
app.add_plugins(states::plugin);
#[cfg(feature = "client")]
app.add_plugins(client_only::plugin);
#[cfg(feature = "dedicated_server")]
app.add_plugins(ds_only::plugin);
}
#[cfg(feature = "client")]
#[path = ""]
mod client_only {
use super::*;
pub fn plugin(app: &mut App) {
app.add_plugins(states::client_only::plugin);
}
}
#[cfg(feature = "dedicated_server")]
#[path = ""]
mod ds_only {
use super::*;
pub fn plugin(app: &mut App) {
app.add_plugins(states::ds_only::plugin);
}
}
Thanks :D
Does your game support a single-player mode? If so, I suppose that you need both the client and dedicated_server features to be enabled when building the client binary. Also, with your architecture, it seems that splitting the project into multiple crates wouldn’t be easy. By the way, did you encounter any complication with this architecture (e.g. compilation time)?
Does your game support a single-player mode?
Single player is just connecting to a local lightyear server. There was a configuration we had in the past that wouldn't even attempt to create the socket, but that hasn't been patched on our end since the last major upgrade to lightyear.
If so, I suppose that you need both the client and dedicated_server features to be enabled when building the client binary.
Nope, "server" code is available to the client for peer based networking, but dedicated_server code is specific to a headless server. We have states for making the server standby and a custom runner to limit the tick rate.
Also, with your architecture, it seems that splitting the project into multiple crates wouldn’t be easy
Probably not, but I do have separate macro crates for both procedural and declarative macros. I don't foresee a necessity to make more crates in this repo personally. Anything that would be a shared library with other games would live in a different repository completely and then would become a dependency.
By the way, did you encounter any complication with this architecture (e.g. compilation time)?
This is hard to quantify. I don't have any equally scaled projects to check against. A majority of the compilation time is dependencies from a fresh build, and we use bevy dylib to cut down on incremental compilation time. I imagine you could use hot patching to avoid most of the compile time we experience.
We don't hot patch (yet) because we like to run in debug mode. That's a whole separate topic that I have done a lot of work to optimize.
Single player is just connecting to a local lightyear server.
Currently, I use crossbeam channels to communicate with the internal server, which is a headless bevy app launched in a separate thread.
I don't foresee a necessity to make more crates in this repo personally. Anything that would be a shared library with other games would live in a different repository completely and then would become a dependency.
Yeah, I can understand that. From what I understand, splitting a large project into separate crates can help with compilation times, since different crates can be compiled in parallel.
This is hard to quantify. I don't have any equally scaled projects to check against.
Fair 😅
We don't hot patch (yet) because we like to run in debug mode.
If I recall correctly, hot patching doesn’t work well with workspaces, so that’s actually a plus for your architecture 😄
hot patching doesn’t work well with workspaces
This could probably be worked around by using just conditional compilation with a main.rs instead of a library crate and separate bin files. This organizational pattern wouldn't see much change otherwise I don't think.
Edit: Oh wait, you mean workspaces with the other organizational patterns. Yes, this would be a bonus to my pattern. You do need to adapt it to not be a library crate though since that won't work with current hot patching.
different crates can be compiled in parallel.
Theoretically you could make data it's own crate, and then each feature in systems it's own crate that all depend on the data crate. Although, I would be curious to see if this actually improves any compile time, since the modules in src/systems don't depend on each other, I would think they would already be processed in parallel.
Hello, i'm running some lightyear examples, and I can't seem to figure out how to select a particular transport.
I skimmed through the book and examples but couldn't find an answer.
Hi, I'm want to use .glb model of a map for the game with lightyear. But I have problem with headless mode and with Replicating of the collider.
So ColliderConstructorHierarchy doesn't work in headless mode. Is there some other way to get collider from custom model and replicate it to client?
commands.spawn((
SceneRoot(assets.load("maps/playground.glb#Scene0")),
RigidBody::Static,
ColliderConstructorHierarchy::new(ColliderConstructor::ConvexHullFromMesh),
Name::new("Floor"),
Replicate::to_clients(NetworkTarget::All),
FloorMarker,
));
I wouldn't replicate a collider or anything that would be only on the client. I would replicate a marker component (I guess in this case it might be FloorMarker?) and then have an observer watch for when that is created on the client and attach your colliders then using the following:
commands.entity(trigger.entity).insert((
ColliderConstructorHierarchy::new(ColliderConstructor::ConvexHullFromMesh,
// other components here to add on the client...
));
I believe this is referred to as the "blueprints" pattern.
You could also do something like what I mentioned in an earlier message using required components and conditional compilation to have components attached in a client-only context.
#1189344685546811564 message
bump this question
Also maybe i'm just slow but I'm trying to run the avian 3d character example locally with one host-client instance and one client instance, but i'm not sure how I'm supposed to connect the two, clicking start does nothing.
You can change the transport used here: https://github.com/cBournhonesque/lightyear/blob/main/examples/common/src/cli.rs#L126
https://github.com/cBournhonesque/lightyear/blob/main/examples/common/src/cli.rs#L173
I’m trying to make clients predict their own character locally while interpolating other characters. I’m using avian3d.
Since I can’t figure out how to make this work properly in my project, I’m trying to make the avian_3d_character example use this behavior instead.
Am I right in thinking that I need to replace PredictionTarget::to_clients(NetworkTarget::All) with PredictionTarget::to_clients(NetworkTarget::Single(client_id)) and also add InterpolationTarget::to_clients(NetworkTarget::AllExceptSingle(client_id)) in server::handle_connected?
I’ve also replaced Added<Predicted> with Added<Replicated> in client::handle_new_character.
First I launch the server:
then connect the first client: (no problem)
then the second client and things start to get weird
I can also end up in this situation by connecting/disconnecting the client in specific order
Yes that's the idea, I think the simple_box example does that: predict your own player and interpolate others
yep, I’ve already looked at simple_box to adapt avian_3d_character. I think my issue is that simple_box relies on gizmos for rendering and also doesn’t use Avian.
The "bug" where a client can’t see both characters can be "fixed" by moving the character on its own client. Once that happens, it suddenly appears on the other client.
It feels like some component is missing or not initialized when it's first spawned.
@pine cape I have a fork that has multi transport capabilities worked out. If you are interested I can make a pr 🙂 It works with all transports but I'm unfamiliar with using steam in that manner or if that would be desired to have as well so I left it out for now.
sure i'm interested; what kind of changes did you make?
Maybe just share a first draft (without polishing) so that we can discuss about the initial direction/design
Sorry for the delayed reply kid had me pretty busy yesterday. The main change is separating the logical server from the transport layer. I went over your notes in the issue on github and decided to approach it the same way, which aligned pretty closely to how mirror/fishnet and other libs handle it.
Instead of the server being tied to one transport, you spawn a single Server entity, then spawn separate transport entities (UDP, WebTransport, WebSocket) that all point to it via TransportOf::new(server).
When clients connect, I had lightyear add a ViaTransport component to each client link entity - this tells you which transport they came through. I used this to name players by their transport ("UDP", "WT", "WS") and assign different colors.
For network clients (UDP/WS/WT) you use the full transport stack with NetcodeClient, ClientUdpIo, etc. For host-client mode (server + client same app), the client just spawns with LinkOf { server } pointing directly to the server - no transport components needed.
I will get a first draft version worked out I wanted to make sure this had nearly complete code coverage before even mentioning it lol I'm still going over each example to test for functionality and the lightyear tests all seem to pass.
It also required a change to how we checked for started since the server technically is not really a netcode server itself any more and had to insert that component manually. So when we trigger Start on the transport entities, Started gets added to them, not to the Server entity.
for example:
fn handle_connected(
trigger: On<Add, Connected>,
client_query: Query<(&RemoteId, &LinkOf, &ViaTransport), With<ClientOf>>,
...
) {
let Ok((remote_id, link_of, via_transport)) = client_query.get(trigger.entity) else { ... };
let server_entity = link_of.server;
// Use ViaTransport to identify the transport
let player_name = transport_names.get(via_transport.transport)
.map(|n| /* ... determine UDP/WT/WS from name */ )
}```
that part is was mainly just a poc and functionality check.
commands.entity(server).insert(Started);```
First, figure out whether the lag is network-bound or simulation/physics-bound.
- Network vs sim
- Run the client with the
lightyear/debugfeature enabled and watch the traffic stats. - If received bytes are low during the lag, it’s likely not the network, it’s probably your physics/simulation work.
- If it’s simulation/physics
- Add timing logs around your physics loop (AI/pathing/distance calcs) to pinpoint what’s taking time.
- If it’s network-related
- Lower TPS
- Send targets less often: instead of updating zombie position every tick, update the goal/target every N ticks or on ticks with special events. On the client, move zombies toward that target using the same movement rules as the server (so visuals stay consistent).
You can keep a non-replicated “visual/estimated position” component client-side while the server maintains the authoritative position. - Visibility System: only replicate the closest N zombies (or those within a radius), and stop sending updates for the rest.
thank you so much
Does anyone know if lightyear supports avian3d with the f64 feature? If I use the f64 for avian3d, it seems that both f32 and f64 are used, because the "avian3d" feature in lightyear seems to use f32, so I get conflicts.
[dependencies]
bevy = "0.17.3"
lightyear = { version = "0.25.5", default-features = false, features = [
"client",
"server",
"netcode",
"replication",
"udp",
"leafwing",
"avian3d",
] }
lightyear_frame_interpolation = "0.25.3"
avian3d = { version = "0.4.1", default-features = false, features = [
"3d",
"f64",
"parry-f64",
"parallel",
"serialize",
"collider-from-mesh",
"debug-plugin",
"default-collider",
#"xpbd_joints",
"bevy_scene",
#"bevy_picking",
]}
leafwing-input-manager = "0.19"
clap = { version = "4.5.53", features = ["derive"] }
serde = "1.0.228"
├── avian3d feature "f32"
│ └── avian3d feature "parry-f32" (*)
├── avian3d feature "f64"
│ └── avian3d feature "parry-f64" (*)
├── avian3d feature "parallel"
├── avian3d feature "parry-f32" (*)
├── avian3d feature "parry-f64" (*)
└── avian3d feature "serialize"
I'm the author of bevy-tnua, and I've just released version 0.27. This version is a big refactor, and one of its implications is that TnuaController can now be serialized and deserialized (TnuaGhostOverwrites too, if you use ghost sensors)
I understand that the inability to do so was what stopping Tnua from being used with Lightyear. So it should work now. I haven't tested it myself (I don't really do networking) but I know that this topic was brought several times both here and in Tnua's own channel (thread, really) so I'm sure some of you will want to try it out - can you please tell me if it works? If not - I'm willing to try and assist on my side.
Please note that in order to make TnuaController serializable and deserializable, you need to:
- Turn on the
serializefeature in bevy-tnua. - Use
#[scheme(serde)]when deriving the control scheme. #[derive(Serialize, Deserialize)]on the control scheme (Tnua's own derive macro cannot automate this for you)
using the native input handling, how would I best go about transforming a picking event such as On<Pointer<Click>> to an action?
do I just turn it into a buffered event or so? maybe just write to a resource I can read from, since I won't be needing to process more than one per frame?
fn buffer_action(
mut player: Single<&mut ActionState<Action>, With<InputMarker<Action>>>,
mut current_action: ResMut<CurrentAction>,
) {
player.0 = current_action.0.clone();
current_action.0 = Action::None;
}
I guess this works.
Yes separating the transport-server layer from the logical server layer seems ideal to me!
The one thing i'm wondering is if we still need to make everything entitified.
In 99% of cases users would only want a single Server entity, in which case the Server logically oversees all the Link entities in the app, and we wouldn't need to check for ViaTransport. But that's an even bigger change..
Thanks, will check it out! The TnuaController is the only thing that needs to be serialized? There is no additional state?
i.e. if I rollback on the client to a previous version of TnuaController, I will be able to deterministically get the same behaviour again?
sorry maybe this fixes it? https://github.com/cBournhonesque/lightyear/pull/1371
Yes - unless you are using TnuaGhostOverwrites, in which case you need to synchronize it as well. The other important components Tnua uses don't need synchronization:
- The sensors and trackers don't need synchronization because Tnua will just read into them from the physics engine which should already be synchronized.
- The motor does not need synchronization, because the controller overwrites its state every single frame.
Though... I think I messed up a bit. I've skipped some of the fields in the controller, reasoning that there is no need to synchronize them: https://github.com/idanarye/bevy-tnua/blob/b56c4c0c1bb262bc607f9a33d33f1b897869ff43/src/controller.rs#L153-L163
But... now that I think about it, if the controller is serialized as a whole, this just means that they'll be set to their default! This is bad.
I'm mainly concerned about these two fields:
sensors_entitiesis a struct which holdsEntityfields. Can Lightyear synchronizeEntityvalues? Do I need to do something special to support this?configholds anHandle. Can Lightyear synchronize these? Unlike entities, Bevy does not implementSerialize/Deserializeon handles even when the feature flag is enabled.
If it's possible to synchronize without adding a dependency on Lightyear then I can make a bugfix. If not, I'll have to move them outside the controller which means a new release.
Does lightyear have any infra for debugging mispredictions? I'm trying to network bevy_ahoy, and am currently just logging everything on the server, logging mispredictions on the client, and then manually trying to match them up and find where things are going wrong.
I'm currently thinking about having the client send a message to the server with a bunch of debug data for a replay of a rollback, keeping a buffer of debug data on the server, and then when the server receives one of these replay rollbacks, automatically align and diff them. That's a lot of infra though so I'm hoping there's existing tooling here?
In theory this could also be a crate all on its own
Honestly the main thing that would be annoying is just building the server-side state buffer, since we need to do like a rolling purge or something.
Hey all, running into an issue that I can't quite figure out, and wondering if anyone has encountered something similar.
For some reason, a system that I have that runs in FixedPostUpdate, right after the PhysicsSystems, seems to be running faster on the Client than it does on the Server. It looks like the LastPhysicsTick on the client generally outpaces the server, despite both the client and the server having the same fixed timestep and both using PhysicsPlugin during FixedPostUpdate. If anyone has any ideas, I'd really appreciate it!
Okay - I've looked a bit into Lightyear documentation and it doesn't look like either of these can be replicated. I'll move them to separate components (which means a new version)
It fixes the conflict, however I get this error (at least when compiling my project):
error[E0277]: the trait bound `DVec3: From<Vec3A>` is not satisfied
--> C:\Users\Rask\.cargo\git\checkouts\lightyear-16a1ca81dacb5ed5\c8a0dce\lightyear_avian3d\..\lightyear_avian\src\correction_3d.rs:143:63
|
143 | &Position(visual_correction.error.translation.into()),
| ^^^^ the trait `From<Vec3A>` is not implemented for `DVec3`
|
= help: the following other types implement trait `From<T>`:
`DVec3` implements `From<(DVec2, f64)>`
`DVec3` implements `From<(f64, f64, f64)>`
`DVec3` implements `From<BVec3>`
`DVec3` implements `From<BVec3A>`
`DVec3` implements `From<IVec3>`
`DVec3` implements `From<Matrix<f64, Const<3>, Const<1>, S>>`
`DVec3` implements `From<OPoint<f64, Const<3>>>`
`DVec3` implements `From<Translation<f64, 3>>`
and 4 others
= note: required for `Vec3A` to implement `Into<DVec3>`
= note: the full name for the type has been written to 'C:\Users\Rask\Documents\GitHub\space_western\target\debug\deps\lightyear_avian3d-cae87f3e37112bfa.long-type-7378993982440218206.txt'
= note: consider using `--verbose` to print the full type name to the console
For more information about this error, try `rustc --explain E0277`.
swapping the .into() for .as_dvec3() like this seems to do the trick&Position(visual_correction.error.translation.as_dvec3())I am not sure if this causes any complications, but it seems to work
I've just released version 0.28 which splits these fields to components of their own (which don't require serialization)
@pine cape - my original promise should be fulfilled now - it should just work if you synchronize TnuaController (and TnuaGhostOverwrites too, if you use it)
how can i replicate Transform only once ? When the entity is spawned
2026-01-05T20:40:49.551077Z WARN bevy_ecs::hierarchy: warning[B0004]: Entity 1091v361 with the GlobalTransform component has a parent (522v8) without GlobalTransform.
Getting this error kind of annoying
I spawn a SceneRoot as children to replicated entity
No there's only the lightyear/debug feature; to debug mispredictions I also mostly put logs on both client and server and match them up. Having better tooling would be immensely useful
Hm maybe LastPhysicsTick and Time<Physics> need to be added to the list of resources to rollback here? https://github.com/cBournhonesque/lightyear/blob/c3c6150cf542762e99b767067366168a7483e1b7/lightyear_avian/src/plugin.rs#L299
Could you try that?
Something like that shoudl work: https://github.com/cBournhonesque/lightyear/blob/c3c6150cf542762e99b767067366168a7483e1b7/examples/avian_3d_character/src/server.rs#L78
@opaque wing I have this draft PR adding a tnua-based example: https://github.com/cBournhonesque/lightyear/pull/1373
So only the Controller component needs to be networked? or the config as well?
The Config is an asset that probably needs to be added on both client and server?
Thanks for the suggestion! I made the change and didn't see anything change on my end, but it turns out that only Time<Physics> can be added because LastPhysicsTick is not Clone. I'm working on some tests in my game and on the side in a clean project to try and pinpoint what's going on and will report back if/when I can find something
How fast is the client outpacing the server?
To keep the client and server timelines in sync, the client slightly slows down or speeds up to stay close to the server. So if it's only going up to 5-10% faster it can be expected
Well the weird thing about it is that I see it happen even before the client has actually connected to the server, even though PhysicsPlugins are added in a shared plugin, and I use a single const tickrate when building Lightyear's server and client plugins. I'm sure it's some bug on my end, just not really sure what I did to make that happen
Yes. I did not see a way to replicate assets and handles in Lightyear's documentation - and frankly, it makes no sense to replicate them, considering they are supposed to be loaded from files that both client and server should have - so I've split TnuaConfig out of TnuaController specifically so that you won't have to replicate it.
how can i calculate delta_secs using localtimeline's tick ?
2026-01-08T10:51:29.894527Z ERROR lightyear_deterministic_replication::checksum: Checksum mismatch from client RemoteId(Netcode(2588418976474603113)) at tick Tick(3423): expected bb8c9df5f7b87b17, got 23c720cc8538a715
I'm trying to make deterministic prediction but hashes in server and client are different somehow
161010383619278091)) at tick Tick(9597): expected d6f5011c0a62eb29, got 8aaae8d262dc4bb6
2026-01-08T11:13:11.895257Z ERROR lightyear_deterministic_replication::checksum: Checksum mismatch from client RemoteId(Netcode(17161010383619278091)) at tick Tick(9598): expected d6f5011c0a62eb29, got 8aaae8d262dc4bb6
2026-01-08T11:13:12.062390Z ERROR lightyear_deterministic_replication::checksum: Checksum mismatch from client RemoteId(Netcode(17161010383619278091)) at tick Tick(9600): expected d6f5011c0a62eb29, got 8aaae8d262dc4bb6
this error happens if i use
InputTimelineConfig::default().with_input_delay(InputDelayConfig::no_prediction())
and rollbacks not working
You can convert the tick to a duration with the TickDuration
can you give an example like how to take a delta_secs using this
I'm sharing my experience with lightyear:
Using the provided generate.sh for the cert was greeting me with a panic: "failed to parse private key as RSA, ECDSA, or EdDSA") ; I noticed it was working when using the self signed from examples, so I wrote a script to use that approach: https://gist.github.com/ThierryBerger/c905fcec169a1e706865e87947f0ce84
Weird, the script works for me. But I like your rust version better! Would you mind submitting a PR with it?
On macOS (sequoia 15.6.1), the generate.sh script was generating incorrect certificates (or at least refused when running server examples), I noticed that using the self signed version of the commo...
wip but serves as a discussion starters as well as sharing incorrectly(?) generated certificates
the ci is also currently broken which is a bit of a « bad look » for a crate with such hard work gone into it 🙂 ; let me know if you'd welcome PRs to fix it, and if you have some insights on particular difficulties to it
does lightyear overwrite Time::<Fixed>?
trying to configure my fixed timestep right now, but it keeps getting reset by something
Are you trying to change it at runtime? I literally just do this and it seems to work:
let tick_duration = Duration::from_secs_f32(1.0 / 64.0);
app.insert_resource(Time::<Fixed>::from_duration(tick_duration));
(it works even if I change the tick_duration to 1.0/10.0)
no I'm just inserting a resource at the start, its just not being respected and I can't figure out why
I set it to 1.0 delta, but when i checked in game it was still 0.017
I vendored my dependencies and lightyear seems like the main one that might do something, but I haven't looked too deep into it yet
Oh are you also changing the tick_duration in the ClientPlugins and ServerPlugins?
When i try to interpolate 50+ entities, some of the entities not updated
It looks like lightyear is automatically replicating children of my player (including its mesh). Is there a way to disable that? I only want the root player entity to replicate, and maybe manually mark some children that they also need to replicate
You can add the DisableReplicateHierarchy component to your player.
That's exactly what I was looking for, thank you!
I was doing a full rebuild since I'm moving to a new PC and while watching btop I noticed that the Computronium crate was compiling with up to 7 threads at once. That number increased and then decreased during the build.
Yes it does
Is there a way to send inputs from server to client with bei implementation ?
also can we set a setting to not send not state affecting inputs like 0,0 movement input
HOLD ON a minute i tought periwink was a spider on pfp, now i see its a cat
Hey hey, I was wondering if there's a recommended way for a client to disconnect itself?
Currently I'm sending a request to the server to disconnect. The server then inserts Disconnected on that client entity. However, when I then attempt to reconnect with the same client, the server spawns a new LinkOf, while the old Disconnected one still exists, which gives me the following warning:
Netcode error: ClientIdInUse(Netcode(3))
Should I be despawning the old entity after the client has disconnected? It feels more intuitive that the old one is simply changed back to a Connected state.
I've also tried triggering Disconnect on the client, but that leaves the same client still Connected on the server
Error receiving UDP packet: An existing connection was forcibly closed by the remote host. (os error 10054)
Aha! The client_timeout_secs was set to like 100 seconds, and the client would create a new Client entity when attempting to connect to the server.
@pine cape is there a way to delta compress position and rotation ? also is there a way to disable sending empty inputs ?
If you use BEI, inputs are not sent when the context is disabled
I think both position/rotation already implement Diffable
I mean like sending zero vectors are un-necessary but bei is sending them regardless
is there any predicted despawn component ?
did something change for leafwing inputs? I recently update to the current git version, and im getting errors with
app.add_plugins(leafwing::InputPlugin::<PlayerActions> {
config: InputConfig::<PlayerActions> {
rebroadcast_inputs: true,
..default()
},
});
What kind of errors? Lightyear might not be compatible with the latest leafwing version, I haven't upgraded to 0.18
Yes, PredictionDisable I think
updating leafwing to 0.19 seemed to fix it
also when tryin go figure out in a system whether it is the server or client
has_server: Single<Has<Server>,Without<ClientOf>>,
seems to make systems be skipped, does anyone have a full proof way to check if its a server or client in a system
btw any estimate for the new release date? absolutely no pressure, just curious when to prepare myself for the 0.18 update for my game :D
and if there are any blockers, like other dependencies that are to be updated to 0.18, I'm also happy to help with PRs to their repos
I'm preparing a branch now; the tests pass but for some reason things are not displayed on screen when i run examples
I wonder if there's some feature name that changed
@silent patrol released 0.26!
is it just me? i cannot compile lightyear with bevy 0.18.0.
i tried clearing cargo cache, cargo.lock, cargo clean. none of that worked
lightyear version is 0.26.0
oh nvm
it suddenly works
i probably did something wrong
[[package]]
name = "lightyear"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "215fe4678b72af3e6e34a3d9605a5b96685666df408e9d0ad685ab02a6815142"
dependencies = [
"aeronet_io",
"bevy_app 0.17.3",
"bevy_ecs 0.17.3",
Seems like lightyear is still pulling some bits of Bevy 0.17
Seems like it's pulling Bevy 0.17 because of bevy_web_keepalive
Compiling lightyear with only default features works.
But when I enable avian3d feature it doesn't.
Oh wait. Even when it compiles, two different bevy versions are used.

published a PR: https://github.com/Nul-led/bevy_web_keepalive/pull/23
@stiff quiver apologies for the tag
Oh thank you for the headsup / pr, yeah im not really active here anymore so didnt notice bevy has been updated again ^^
Thanks, will push an update tonight
actually i need the new version of bevy_web_keepalive to be released first
maybe the easiest is to just vendor it
thanks for the patch!
@pine cape oh you still have bevy_web_keepalive in the dependency list, which keeps pulling bevy 0.17 :D
from my Cargo.lock:
[[package]]
name = "lightyear"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9210597b60f7a3787fec3f4b07782861f3c1b1a5e19056f4f0ff260f8e193990"
dependencies = [
"aeronet_io",
"bevy_app 0.18.0",
"bevy_ecs 0.18.0",
"bevy_web_keepalive",
i'm not sure how that's possible.. cargo tree | grep bevy_web_keepalive or cargo tree -i bevy_web_keepalive returns nothing for me
i do s it listed in the deps on crates.io
published another update; tat one should be good :p
Is there a minimal example which has both a client and server running at the same time?
I'm trying to make a client-hosted server, but would rather be consistent and use the same messaging interface for the "local player", even in singleplayer.
I tried looking at the lobby example on github but there's so much going on in there.
I can confirm it's fixed now, thanks again! :)
is there an example for mapping entities in events/components/relations?
Try running with host-client https://github.com/cBournhonesque/lightyear/tree/main/examples#running-an-example
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
yeah you just need to call add_map_entities on the thing to replicate, like here: https://github.com/cBournhonesque/lightyear/blob/main/examples/replication_groups/src/protocol.rs#L252
Found a bug in Lightyear
Summary
- On first connection / initial relevance, entities with
NetworkVisibilityreplicate normally and clients can see them. - As soon as an entity loses network visibility once, it can never become visible again even when
ReplicationState::gain_visibilityis called.
Suspected flaw
In visibility/immediate.rs, NetworkVisibilityPlugin::update_network_visibility deletes the entire per-sender entry when visibility is Lost:
if state.visibility == VisibilityState::Lost {
return false; // deletes PerSenderReplicationState
}
In the comment above it's mentioned that this could possibly be unsafe, but for some reason it was never an issue before?
Workaround
I stopped deleting the per-sender entry on Lost. Instead, the entry is kept and we only update the visibility state.
if state.visibility == VisibilityState::Lost {
// We already sent DESPAWN, but keep the entry so we don't lose metadata
// like predicted/interpolated (and authority).
state.visibility = VisibilityState::Default;
// We want a future gain_visibility() to cause a SPAWN again.
state.spawned = false;
return true;
}
This makes my case work again, but I'm not sure if this is actually a good fix. I did not have this issue in 9266179 which is odd.
Also worth noting: update_network_visibility only mutates/cleans entries when the sender’s send_timer.is_finished(), maybe this was altered when updating to 0.18?
Correction: this was an issue in 9266179, I just never noticed it
When update_network_visibility drops the PerSenderReplicationState on Lost, the next gain_visibility() recreates the sender entry via per_sender_state.entry(sender).or_insert_with(PerSenderReplicationState::with_authority).
That constructor recreates a “blank” per-sender state (interpolated=false, predicted=false), and the visibility helpers only update visibility, they don’t re-apply InterpolationTarget / PredictionTarget.
Since those flags are normally set only by Replicate/ReplicationTarget insert/connection logic the recreated entry keeps incorrect defaults, leading to incorrect respawn behavior after a visibility loss.
Your fix seems sensible to me, I would have to think about it more
https://github.com/cBournhonesque/lightyear/pull/1393 FYI - lmk if you'd like to see any changes or if i'm thinking about things wrong
LGTM! out of curiosity, what kind of user-data are you sending?
the meetup talk was great!
Thanks! Was nervous about it haha
Was this recorded? I'd love to watch it!
youtube, so lemme grab the link
Meetup event: https://www.meetup.com/bevy-game-development/events/312681343
Live-Ask Questions Board: https://ezli.me/6wmav5
at that timestamp (although the dependency injection talk today was good too and came earlier in the stream)
Q: In older versions, after a client connects the other players would be in their correct locations. Now when a client connects, everyone is spawned at 0,0 instead of their server location.
Is there a way to sync all the player positions on connect? I'm not sure what changed.
I am guessing there is a new way to say "Start with state replication"
lightyear docs are broken btw 😢
Is this only a problem in 0.26?
Started in 0.25
I didn't notice this personally in 0.25? Maybe you have a system ordering issue like your replicated component isn't being set on spawn?
I have a when a player connect, the server adds a
#[derive(Bundle)]
pub struct PlayerBundle {
pub name: Name,
pub player: PlayerId,
pub appearance: PlayerAppearance,
pub movement_target: PlayerMovementTarget,
}
The movement target is an Input for BEI, e.g.
/// Player input context used by Bevy Enhanced Inputs
#[derive(Component, Serialize, Deserialize, Clone, Debug, Default, PartialEq, Reflect)]
pub struct PlayerMovementTarget(pub Option<Vec2>);
#[derive(Debug, InputAction)]
#[action_output(Vec2)]
pub struct ClickToMove;
and finally, they are admitted and replicated like this:
fn admit_client(
commands: &mut Commands,
sender: &mut ServerMultiMessageSender,
server: &Server,
client_entity: Entity,
client_peer_id: PeerId,
player: PlayerBundle,
) {
let entity = commands
.spawn((
player,
Replicate::to_clients(NetworkTarget::All),
PredictionTarget::to_clients(NetworkTarget::Single(client_peer_id)),
InterpolationTarget::to_clients(NetworkTarget::AllExceptSingle(client_peer_id)),
ControlledBy {
owner: client_entity,
lifetime: Default::default(),
},
))
.id();
lmk if anything here looks weird
Are you using avian?
I think it's because avian's sync from Position/Rotation to Transform is only done when both Position/Rotation are present
your entity might get spawned with only Position (maybe because of interpolation), so it starts by getting spawned at (0, 0)
I agree that it is frustrating, and it can be hard to prevent
I have a scheme sorta like this, but I don't spawn a player before admitting the player - I instead send raw messages to get them in agreement and then I spawn the player then
I wonder if something goes a little wrong since your player exists early?
So to fix, I make the player require components with a Position and Rotation?
It should work yea; because then Pos/Rot are inserted together (i'm assuming you're missing Rot) so your Transform will get updated by avian
I have both Position/Rotation registered, and required components. Still seeing the behavior.
I think it's because the confirmed position is not updating on the server, not sure why.
The green line shows where the server position is (origin)
Inputs still trigger on both clients, but yeah Im not sure why the server isnt updating the players' positions
Weird
@pine cape ought of curiosiry why did you develop lightyear what was your motivation for such an ambitious cratem
I wanted to make a clone of the game powerline.io
Oh I guess that scope was reached
Do you think this is error is something on my end? I'm in the process of updating to bevy 0.18
2026-01-31T06:10:48.941116Z INFO lightyear_netcode::client_plugin: Client Netcode(3466457520131969991) connected
thread 'main' (48676) panicked at C:\Users\eliza\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\bevy_ecs-0.18.0\src\error\handler.rs:125:1:
Encountered an error in command `<bevy_ecs::system::commands::EntityEntryCommands<'_, bevy_ecs::hierarchy::Children>::and_modify<<bevy_ecs::hierarchy::ChildOf as bevy_ecs::relationship::Relationship>::on_insert::{{closure}}>::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: Entity not yet spawned: The entity with ID PLACEHOLDER is not spawned; enable `track_location` feature for more details.
Note that interacting with a not-yet-spawned entity is the most common cause of this error but there are others
If you were attempting to apply a command to this entity,
and want to handle this error gracefully, consider using `EntityCommands::queue_handled` or `queue_silenced`.
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `lightyear_replication::receive::ReplicationReceivePlugin::apply_world`!
thread 'main' (48676) panicked at C:\Users\eliza\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\bevy_ecs-0.18.0\src\error\handler.rs:125:1:
Encountered an error in command `<bevy_ecs::system::commands::entity_command::insert_with<bevy_ecs::hierarchy::Children, <bevy_ecs::hierarchy::ChildOf as bevy_ecs::relationship::Relationship>::on_insert::{{closure}}>::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: Entity not yet spawned: The entity with ID PLACEHOLDER is not spawned; enable `track_location` feature for more details.
Note that interacting with a not-yet-spawned entity is the most common cause of this error but there are others
If you were attempting to apply a command to this entity,
and want to handle this error gracefully, consider using `EntityCommands::queue_handled` or `queue_silenced`.
Encountered a panic when applying buffers for system `lightyear_inputs::server::receive_input_message<lightyear_inputs_leafwing::input_message::LeafwingSequence<gnome_player::protocol::PlayerActions>>`!
Encountered a panic in system `bevy_ecs::apply_deferred`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
error: process didn't exit successfully: `target\debug\bevy-code-gnome.exe client` (exit code: 101)
the interesting thing is it comes from lightyear_inputs::server::receive_input_message, but its on the client
ripping out my world plugin seemed to fix it so i think its on my end
Yeah these warnings were also happening in 0.25
yea you can ignore these warnings and hide them
Yea RUSTLOG
user id + display name, maybe more in the future like IDs to look up skins
I have been stuck for a few days trying to make a simple_box example clone to get aquainted with the api of the library to build a third person shooter/hack and slash.
Every time I add linear interpolation the replicated entities stop working (after a very tiny delay). Am I missing a component, am I replicating too fast? (16 ms)
The simple box example does work so I assume I am doing something wrong but I get no output from the interpolation system warning me.
Here I append the whole project without the target directory.
Thanks in advance
If you add prediction to a component, it should not be overwritten by the server, right?
Why is the server overwriting this component when it changes locally? I must be doing something wrong here.
If the client mispredicts, then the client will be rolled back to the server state to make sure things stay in sync
So yes, the server can make the component overwritten.
I don't have rollbacks configured.
Rollback happens automatically regardless AFAIK. How do you mean you "don't have rollbacks configured"?
I mean I didn't add .add_rollbacks or .should_rollback or whatever the function is
I basically gave up on implementing the Input plugin with BEI because for the past year I have never gotten it to work despite feeling like I understand how lightyear should work.
So I am transitioning to having the client send a message that says "Here's where I clicked, please replicate that to other clients".
Thus, the protocol is that I have a "PlayerMovementTarget" component on players that is controlled by the clients and the server just replicates that to other clients
AFAIK that should mean I just need to add prediction
I personally don't like bei and specifically its networking, so I can understand that
I think the "should rollback" function just changes how lightyear decides to rollback - by default I think it uses PartialEq or something
I think you probably want to use the native input plugin
Send the movement target as an input, and that input can be replicated to clients
Can you help me understand how replication works?
I have the server spawn a bundle of things on an entity, e.g.
- Player
- PlayerAppearance
- PlayerMovementTarget
And I want the server to tell all clients that if they do not match the server, they can be overwritten. They should stay synced with the server.
HOWEVER, PlayerMovementTarget is supposed to be an exception. I want the client to own authority over that component specifically, never listen to the server, etc.
How do I stop that component from being changed on the client?
I think you're right about the native input plugin part. For now I want to keep this in messages since I basically don't trust lightyear to do anything but messages correctly for now.
I'm not a lightyear expert, but my understanding is that an entity can't be "shared" like that. It's either entirely owned by the server or the client. The input plugin stuff is special and doesn't use replication afaik
It uses raw messages internally
Does that mean I have to split the entities?
I thought that too, but it's not really clear in docs that I've seen.
The difficulty with messages is they're not synced with the server time line, which means your client may predict differently than the server for a given frame because the message arrived on a different tick.
Yea, fair. Still curious about entity replication
I think so? But I'd seriously suggest you don't do that and instead use the input plugin
One way to stop the server overwriting is to set your component to only replicate once for the player's character
API documentation for the Rust ComponentReplicationConfig struct in crate lightyear.
(I wanna add I suspect it might be because of the extra bad conditions I put on the Link?)
Edit: Tested the box test with the same config and it doesnt desync
In NetworkedPlugin you're adding ClientPlugins and ServerPlugins after registering replicated components. According to their documentation you need to add the client/server plugins before component registration.
Also, I noticed you're adding client and server plugins at the same time. Unless this is meant to be a host client setup, you should gate these behind features so only one is enabled at a time.
I disagree with the last point: while gating is good for dedicated servers, a real game will probably want to change client vs host client based on a menu (like in your menu, you click host game or join game). So your game should be resilient to both plugins being enabled
Rollback is enabled by default
You can disable replication for that movement component
Yeah adding both client and server plugins in the same app doesn't cause any issues
I will change the order of adding the plugins and registering them thanks. Although I already have tried that before and the linear interpolation still breaks after a few ticks.
As for the last point, I plan to slowly build a real game out of it. And realistically I need to use both plugins as the players would be both host clients or just clients. Having to select a type on launch would be cumbersome for the average player. Furthermore, besides inputs being broken in host client mode, everything seemed to work fine on the examples.
Besides changing the order of the plugins (inside the networkedplugin) I havent changed anything else.
It still breaks in this consistent but odd way
Does lightyear support multiple wasm clients to the same server on the same machine?
I think so, because I did that to test wasm
@pine cape can I request a new example to lightyear?
I’ve given Claude code access to lightyear and tried to set it up but no matter what I try it doesn’t seem to work.
I would like a 2D example with native inputs where a player clicks somewhere on the screen and the player walks towards the target until it arrives.
I also would like this to work with avian2d and move the Position but apply to the Transform.
Hello all 🙂
Sorry to bother if it has already been asked before but, how can we test our games with lightyear ?
For now I have a game up and running (I mean : playable, networked, replicated etc..), and I would like to test it and tried like :
#[cfg(test)]
mod test {
#[test]
pub fn test_something(){
let mut server_app = create_server_app(true, NetworkMode::Udp);
// Headless
let mut client_app1 = create_client_app(1, "../../assets".to_string(), true, NetworkMode::Udp);
let mut client_app2 = create_client_app(2, "../../assets".to_string(), true, NetworkMode::Udp);
// app.update() for all
let mut q2 = client_app2.world_mut().query_filtered::<(), (With<Client>, With<Connected>)>();
let c2_connected = q2.iter(client_app2.world()).next().is_some();
// It is connected !
}
}
// But then searching for replicated components I can't find some...
// (Like my custom LobbyState, etc... )
I then run with cargo test -- --test-threads=1 (as asked by bevy)
note: create_client_app is a function that create a bevy app and add all the plugins and systems to make the game run, and it runs well, (headlessly also for tests).
Do you have any recommendation or way to test your games ?
@pine cape @plucky smelt
I have finally found my issue: https://github.com/cBournhonesque/lightyear/issues/1402
I found it because I've been having stutters and rollbacks sporadically with absolute randomness, until I added a 1 tick input delay, which fixed the networking inputs entirely
Also kindof important to note, I was using a 15Hz Tick rate, which made things more clear
I might be remembering this wrong, but lightyear doesn't sync entities across client and server, but uses a lookup table, do we have access to this lookup table?
and by sync entity i mean the entity id
yeah, you can access it via MessageManager::entity_mapper
each peer has MessageManager as a component
you want to run unit tests? or just run it locally?
I run unit tests via https://github.com/cBournhonesque/lightyear/blob/main/lightyear_tests/src/stepper.rs
What part doesn't work? The avian replication or inputs? I have tests for avian here: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_tests/src/client_server/avian/position_replication.rs
It was the issue I filed
Ever since then my entire 1 year feud with inputs stuttering and locking up was fixed
btw, could you upgrade getrandom in lightyear? Ive had to use this explicit dependency because you're pulling in getrandom 0.2 somehow
getrandom = { version = "0.2.16", features = ["js"] }
I think the newest version of bevy is on 0.4 or 0.3 at the least
I did that because it was needed for wasm to work correctly, not sure if things have changed; i'm open to PRs
i'm having trouble trying to understand the minimum parts of the simple_box needed to make things work, and get it into my code
i have made a udp connection between server and client, but both programs give a bunch of errors in the terminal when connected
let me grab the exact error text
also, i have confirmed that the "simple_box" example is building and running just fine so i must be missing something
i tried to copy only what i need from that example but something must not be right
server:
2026-02-13T23:46:08.355222Z ERROR lightyear_transport::plugin: Error processing packet: ChannelReceiveError(MissingMessageId)
client:
2026-02-13T23:46:08.382156Z ERROR lightyear_messages::receive: Error receiving messages: Serialization(BincodeDecode(Io { inner: Error { kind: UnexpectedEof, message: "failed to fill whole buffer" }, additional: 1 }))
client also gets this occasionally:
2026-02-14T00:01:04.106839Z ERROR lightyear_messages::receive: Error receiving messages: Serialization(BincodeDecode(InvalidIntegerType { expected: U16, found: U32 }))
2026-02-14T00:01:04.207238Z ERROR lightyear_messages::receive: Error receiving messages: Serialization(BincodeDecode(InvalidIntegerType { expected: U16, found: U64 }))
2026-02-14T00:01:04.321625Z ERROR lightyear_messages::receive: Error receiving messages: Serialization(BincodeDecode(InvalidIntegerType { expected: U16, found: U128 }))
2026-02-14T00:01:04.407207Z ERROR lightyear_messages::receive: Error receiving messages: Serialization(BincodeDecode(InvalidIntegerType { expected: U16, found: Reserved }))
i am guessing i've somehow messed it up so the protocol is not the same on both? but i haven't figured it out
if anyone wants to look at my spaghetti, the code is hosted here: https://github.com/gardrek/dmak26
i know Rust decently at this point, but i don't understand Bevy much, and Lightyear even less so
it was a mistake to think i could whip this up in a single week long jam lol
i have noticed that the spawn_connections function in cli.rs in the examples/common folder does not ever use UDP, all that is commented out in favor of WebTransport
for the server, specifically
the default features seem to imply that UDP is used by the client but then how can they communicate if the server is using WebTransport?
in the case of the simple_box demo (which i was able to compile and run no problem on my computer)
just stumbled upon this as well. Even though I did setup refreshing connect tokens, apparently, there's still some edge case where I fail to do that?..
anyway, it would be nice to have a way to handle this error and close the connection
atm, both client and server are just stuck in the connecting state (until a 15s timeout?), and the server only spams with the logs Netcode error: Packet(TokenExpired), and I can't find any way to handle this in a more elegant way rather than just waiting for a timeout
you would have to switch both the client and server examples to use UDP instead of WebTransport; you can try it if you want
You would like some sort of callback where you handle the error and potentially close the connection early?
i'm just confused how the example works in the first place, because the UDP code seems to be commented out in cli.rs's spawn_connections function. do i have to edit that to test out UDP?
i think instead i'll change my code to use webtransport if i can
i was planning to do that anyway, i just don't understand the certificate stuff
well, I think lightyear should even close the connection itself in this scenario
I'm not sure if there's any reason to keep a half-established connection if a connect token is expired (unless I'm missing anything?)
but it also would be nice to have some API to report an expired token (as a disconnect reason for example)
I saw there's
pub struct Disconnected {
pub reason: Option<String>,
}
but I don't really like the fact that it's a string, as it makes it difficult to match against possible errors
this would have been a good place to use a non-exhaustive enum instead of a string
that way you can always add new variants without causing old code that uses the library to not compile
it's funny now i can't build the example at all lol
it's just hanging on the final build of the binary
I'm not sure when this bug showed up, but for some reason my client crashes when it joins a server with more than 4 enemies already there. Any guesses what could be causing this? the client terminal log isn't very helpful: (updated with rustbacktrace=1)
Encountered an error in command `<bevy_ecs::system::commands::EntityEntryCommands<'_, bevy_ecs::hierarchy::Children>::and_modify<<bevy_ecs::hierarchy::ChildOf as bevy_ecs::relationship::Relationship>::on_insert::{{closure}}>::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: Entity not yet spawned: The entity with ID PLACEHOLDER is not spawned; its index was last despawned by /Users/elizabethsuehr/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.18.0/src/entity/mod.rs:1234:17.
Note that interacting with a not-yet-spawned entity is the most common cause of this error but there are others
If you were attempting to apply a command to this entity,
and want to handle this error gracefully, consider using `EntityCommands::queue_handled` or `queue_silenced`.
2: core::ops::function::FnOnce::call_once
3: bevy_ecs::world::command_queue::RawCommandQueue::apply_or_drop_queued
4: lightyear_replication::registry::buffered::TempWriteBuffer::batch_insert
5: lightyear_replication::registry::buffered::BufferedChanges::apply
6: lightyear_replication::receive::GroupChannel::apply_actions_message
7: lightyear_replication::receive::ReplicationReceivePlugin::apply_world::{{closure}}
8: <bevy_ecs::system::exclusive_function_system::ExclusiveFunctionSystem<Marker,Out,F> as bevy_ecs::system::system::System>::run_unsafe
9: bevy_ecs::system::system::System::run
note: Some "noisy" backtrace lines have been filtered out. Run with `BEVY_BACKTRACE=full` for a verbose backtrace.
Encountered a panic in system `lightyear_replication::receive::ReplicationReceivePlugin::apply_world`!
Encountered an error in command `<bevy_ecs::system::commands::entity_command::insert_with<bevy_ecs::hierarchy::Children, <bevy_ecs::hierarchy::ChildOf as bevy_ecs::relationship::Relationship>::on_insert::{{closure}}>::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: Entity not yet spawned: The entity with ID PLACEHOLDER is not spawned; its index was last despawned by /Users/elizabethsuehr/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.18.0/src/entity/mod.rs:1234:17.
Note that interacting with a not-yet-spawned entity is the most common cause of this error but there are others
If you were attempting to apply a command to this entity,
and want to handle this error gracefully, consider using `EntityCommands::queue_handled` or `queue_silenced`.
2: core::ops::function::FnOnce::call_once
3: bevy_ecs::world::command_queue::RawCommandQueue::apply_or_drop_queued
4: <bevy_ecs::system::function_system::FunctionSystem<Marker,In,Out,F> as bevy_ecs::system::system::System>::apply_deferred
5: bevy_ecs::schedule::executor::multi_threaded::apply_deferred
6: <async_executor::AsyncCallOnDrop<Fut,Cleanup> as core::future::future::Future>::poll
7: async_task::raw::RawTask<F,T,S,M>::run
8: bevy_tasks::thread_executor::ThreadExecutorTicker::tick::{{closure}}
9: <bevy_ecs::schedule::executor::multi_threaded::MultiThreadedExecutor as bevy_ecs::schedule::executor::SystemExecutor>::run
10: bevy_ecs::world::World::try_schedule_scope
11: bevy_app::main_schedule::Main::run_main
12: bevy_ecs::system::system::System::run_without_applying_deferred
note: Some "noisy" backtrace lines have been filtered out. Run with `BEVY_BACKTRACE=full` for a verbose backtrace.
Hey. Not sure what part you don’t understand around certs but hoping my little finding might help you get setup at least for during your development.
I’ve been working a couple hours today to get webtransport via browser using the wasm client server working locally on my network and came across this something that helped me with bypassing the very strict webtransport requirements for certs in my browser locally:
Chrome: enter chrome://flags and set #webtransport-developer-mode to Enabled```
I think things will be easier to setup in a production environment where you can get real CA certs more easily. At first I tried using self signed CA ones using mkcert but didn’t find any success. Very positive it’s just skill issues on my end and not doing it correctly but at least this workaround for local makes it easier to get on with just working on developing everything first.
And also for lightyear including this feature could help with certain cert issues :
`webtransport_dangerous_configuration`
Oh just noticed there’s a webtransport_self_signed feature flag too. Maybe that’s why my self signed stuff hasn’t been working properly
yeah you have to edit the code if you want to use UDP.
The certificate is just needed for webtransport, and you need to run certificates/genererate.sh beforehand to have a valid certificate
it looks like the entity PLACEHOLDER is being despawned by replication. Maybe there is some entity mapping missing?
what i want is for the client to work on itch.io, and idk if that means i could use a self-signed cert or if i'd have to get some itch-specific thing or what. i haven't tested it yet tho because i have yet to successfully get the server and client actually communicating properly in my code.
Just to be clear what you mean when you say client, do you mean like a browser client or a native client build executable?
Do itch.io support browser games? Is that a platform mostly for game client distribution?
do you think this is a problem on my end?
a browser client running on itch.io's website. built using the wasm32-unknown-unknown target
and yes itch.io supports browser games. and my experience is that most itch users want to play that way, especially for jams
i got a bare bones basic bevy "game" running on itch.io's site no problem, i just have not tried networking because i was first testing native linux client to native linux server
i think now that i have officially missed the jam window, i'm going to just try building and uploading the simple-box demo, see if that just works
unfortunately i am going to ghost in three minutes because i'm in an institution that turns off the wifi at 830
I see. So itch hosts the client, you host the game server right? I’m pretty sure itch io has its own ca certs but you’ll need a ca certificate for your game server that browser clients can trust. For that I’d use certbot. You can eventually also try pointing your wasm client to the same certs using a trunk.toml config file. Kinda like this:
[serve] addresses = ["0.0.0.0"] port = 8080 tls_cert_path = "certificates/cert.pem" tls_key_path = "certificates/key.pem"
Don’t use self signed stuff for itch trying to connect to your game server, it most likely won’t work. That’s best only for local if I’ve understood self signed correctly
Not sure what the port should be here but I’m pretty sure you got that figured out if you successfully put up a wasm client on another bevy project before.
I'm not really sure what a placeholder even is lol
So I asked my friend Claude to fix it and it seems to be working, didnt look into the problem because you're planning the replicon rework, but yeah no need to look into it further as I have a temp solution lol
i am locally running the web build of the client for simple_box, using bevy run web as the readme instructs, and i also ran the server, but i have yet to get them to connect. once the server starts, after trying to connect, the client says this on-screen:
the server i am running as cargo run -- server, and in the terminal it says
INFO lightyear_webtransport::server: Server WebTransport starting at 0.0.0.0:5888
among other things
(the rest of the terminal spew is just regular bevy stuff about the gpu and window)
Try adding this feature to lightyear in your cargo.toml webtransport_dangerous_configuration
If this is a local run. And just incase, you generated self signed certs right using the generate.sh in /certificates right?
ah no i didn't generate the certs
i have just run certificates/generate.sh from the lightyear crate root, will rebuild and test
also earlier i noticed the instructions for how to build a headless server (for simple_box) caused build errors. but the normal build is fine. i think it just needs a couple bevy includes
error: linking with `rust-lld` failed: signal: 7 (SIGBUS) (core dumped)
well that's unusual
i think my disk is full of artifacts lol
need to do it from the example root.
had the same issue. think right now not all examples are updated. see if theres any luck with that using the demo/spaceships one?
building it from the example root just errors, it's looking for the certificates folder
it seems to be having trouble still tho. instead of disconnecting immediately, the simple_box client now sits on "connecting" indefinitely. it's not even timing out
i'll try the spaceships one later
You also did these things to your browser right?
@pliant bison
I had the exact same issues as you. Still a bit unsure how I solved it but try these aswell
Oh yeah you gotta adjust the paths so they’re looking in the right place. You probably solved that already
ah no i didn't do that part
tbh i'd rather just skip all this stuff and test on itch's site directly
Unfortunately that I don’t know how. Haven’t gotten my game to a production worth state so hasn’t tried it on a real domain
Only local network ip address
i changed the browser setting but the behavior is the same, stuck "connecting"
server is unaware of client's existence
I think you might have forgotten to apply entity mapping on one of your messages or components?
To everyone encountering issues; i've just merged this: https://github.com/cBournhonesque/lightyear/pull/1412
I think bevy 0.18 updated the default features, which messed with examples
Then I ran
certificates/generate.sh
and
cargo run --no-default-features --features=server,netcode -- server
bevy run web
to get a client connecting to the server in wasm
for localhost and self-signed cert webtransport these settings certainly helped for me:
this helped stop the indefinitely "connecting" on my end.
Webtransport doesn’t seem to work at all for via Firefox based browsers. So stick to chromium
Well it will work over https I’m sure. But not localhost with self signed.
should i switch to a git version or is this something i can get from crates.io? (with like cargo update?)
i see, hmm. this to me is more evidence i should test actually on the website. cause i don't want to run chromium just for this, and i definitely want it to work on firefox
i think i'll keep looking into certbot and also try to see if anyone has gotten a similar setup working on itch (not with lightyear specifically, just with WebTransport / QUIC)
I got this setup with generated certs working on itch, but I had to refresh certs e ery 2 weeks
really? that's good to know. so this could be great for a weeklong game jam, as everyone is sure to be bored of the game by the two week mark. unless it accidentally becomes a hit lol.
Hi, why does this snippet work well even without the With<Predicted> filter? ```/// The client input only gets applied to predicted entities that we own
/// This works because we only predict the user's controlled entity.
/// If we were predicting more entities, we would have to only apply movement to the player owned one.
fn player_movement(
// timeline: Single<&LocalTimeline>,
mut position_query: Query<(&mut PlayerPosition, &ActionState<Inputs>), With<Predicted>>,
) {
// let tick = timeline.tick();
for (position, input) in position_query.iter_mut() {
// trace!(?tick, ?position, ?input, "client");
// NOTE: be careful to directly pass Mut<PlayerPosition>
// getting a mutable reference triggers change detection, unless you use as_deref_mut()
shared::shared_movement_behaviour(position, input);
}
}
Probably because we only added input handling (the ActionState component) to the predicted entity
is there a migration guide somewhere? I just came back from a long hiatus on my project, and I'm bumping by 1 major bevy (and lightyear) version at a time. I see ServerReplicate has been removed between 0.19 -> 0.24 (as in bevy 0.15 -> 0.16), but I'm not sure what the logic is now
Did something change between 0.25 and 0.26.4 that would have led to the Controlled component not getting added in host-client setups where it would have been added previously?
Anyone here know if lightyear websocket is noticeably less performant than lightyear webtransport or is it nearly the same? Might try websocket instead because of better browser compatibility.
Hi, I encounter following error client-side whenever I spawn an entity with many children (say 1000) on Connected observer:
Encountered an error in command `<bevy_ecs::system::commands::entity_command::insert_with<bevy_ecs::hierarchy::Children, <bevy_ecs::hierarchy::ChildOf as bevy_ecs::relationship::Relationship>::on_insert::{{closure}}>::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: Entity not yet spawned: The entity with ID PLACEHOLDER is not spawned; its index was last despawned by C:\Users\ZX\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\bevy_ecs-0.18.0\src\entity\mod.rs:1234:17.
Note that interacting with a not-yet-spawned entity is the most common cause of this error but there are others
Spawning looks like this
pub(crate) fn handle_connected(
trigger: On<Add, Connected>,
query: Query<&RemoteId, With<ClientOf>>,
mut commands: Commands,
) {
let Ok(client_id) = query.get(trigger.entity) else {
return;
};
commands
.spawn((
Replicate::to_clients(NetworkTarget::All),
))
.with_children(|spawner| {
for _ in 0..1000 {
spawner.spawn(());
}
});
}
For low numbers this does not crash. Does anyone might know what could be wrong?
I would say the easiest is to look at the examples
It is possible that a bug was introduced, the host client logic is a bit brittle
You may want to set a ReplicationGroup for the parent and the children so that they are spawned in the same message
Thank you, somehow I assumed this should be guaranteed by default for hierarchies but now that I think about it, it probably wouldn't be the best thing to do in general.
Hi everyone! I'm hitting a consistent panic when using Lightyear with Avian 2D for a multiplayer project.
The Error:
called Option::unwrap() on a None value
Encountered a panic in system avian2d::collision::narrow_phase::update_narrow_phase<avian2d::collision::collider::parry::Collider, ()>!
Context:
The panic occurs on the Server when a player "pushes" hard against a static collider (a wall). It seems to be related to Lag Compensation / Rollback.
I suspect that when Lightyear triggers a rollback to a previous tick, the sudden "teleportation" of the Position component confuses Avian's internal contact island solver, as it tries to reconcile new contacts with cached ones from a future that was just rolled back.
My Setup:
Bevy latest
Lightyear: Latest
Avian 2D: latest
The player has : collider: Collider::rectangle(PLAYER_SIZE, PLAYER_SIZE),
collider_density: ColliderDensity(1.0),
rigid_body: RigidBody::Dynamic,
restitution: Restitution::new(0.0),
constraint: LockedAxes::new().lock_rotation(),
dumping: LinearDamping(10.0),
swept: SweptCcd::default(),
I am using LagCompensationHistory on the player entity.
What I've tried:
Disabling LagCompensationHistory stops the panic, but I need it for hit registration.
I suspect my SystemSets might be ordered incorrectly.
Question:
How should I correctly sync Avian's physics step with Lightyear's FixedUpdate loop to prevent the solver from panicking during rollbacks? Should I be using separate hitboxes for lag compensation instead of the main physics body?
Thanks in advance! P.S. Maybe there is a way to create my own HistoryBuffer<>?
Have you disabled Islands? Indeed the islands plugins breaks down during rollback because some of its internal invariants are broken
This happens even without lag compensation being enabled
@pine cape Is it still the plan to replace part of lightyear internally with replicon, per your draft PR from December? I'm trying to pick between lightyear and replicon, and if lightyear ends up being a layer on top of replicon that makes it an easy "use lightyear if I need what it adds, otherwise replicon" decision
I was also curious about this! Not that I’m switching, I love lightyear, I just wanna know when/if I should expect a big change
I'm using lightyear 0.26.4. I'm spawning 100 "creep" entities in my game with an interpolated EntityPosition component, and all entities are in the default ReplicationGroup.
I found that many of these entities are only updated once every few seconds.
My ReplicationSenders look like this, with no bandwidth cap:
ReplicationSender::new(Duration::from_millis(33), SendUpdatesMode::SinceLastAck, /*bandwidth_cap_enabled*/ false),
I've solved this by adding ReplicationGroup::new_from_entity() to every creep that I'm spawning, but it leaves a few questions:
- Should this be happening with the default
ReplicationGroup? I understand that entities in the sameReplicationGroupare updated atomically(?). Is this just expected behavior, then, or is it a bug? - Should entities be contained in their own
ReplicationGroupby default, rather than sharing one big default group? If not, should the examples or documentation call out that you likely want to put entities in their own groups?
I've attached a couple of videos showing the effects with and without individual ReplicationGroups.
I think I've confirmed that this is the case. Running the bevy_enhanced_inputs example in 0.25.5 works, but running the same example on 0.26.x or main does not because handle_predicted_spawn is checking for Has<Controlled> Is there something you could point me to that I could try fixing the issue? Unfortunately, this is the only thing blocking us from upgrading to 0.18 and is starting to become too difficult to hold off.
I may have found part of the issue. At least for Controlled - in lightyear_replication/src/host.rs HostServerQueryData.replicate_like is not an Option, so root_query is empty.
This solves Controlled not existing for our implementation, but the bevy_enhanced_inputs example is still broken. I'm not sure why, as I'm using BEI in Computronium and it seems to work when changing replicate_like to an Option.
The bevy_enhanced_inputs example has additional problems, even outside of host-client mode. With a server and two clients I get a lot of log error spam and the movement becomes choppy.
It's fine when there's a single client FYI.
Relevant log lines:
2026-03-09T22:08:18.290007Z INFO bevy_winit::system: Creating new window Lightyear Example: lightyear_examples_common (0v0)
2026-03-09T22:08:18.292707Z INFO lightyear_netcode::client: client connecting to server 127.0.0.1:5888 [1/1]
2026-03-09T22:08:18.292730Z WARN lightyear_webtransport::client: Connecting with no certificate validation
2026-03-09T22:08:18.635070Z INFO lightyear_netcode::client_plugin: Client Netcode(0) connected
2026-03-09T22:08:18.868856Z WARN bevy_enhanced_inputs::client: Add InputMarker to entity: 183v0
2026-03-09T22:08:41.638379Z WARN bevy_enhanced_inputs::client: Add InputMarker to entity: 189v0
2026-03-09T22:08:42.139276Z ERROR lightyear_inputs::client: received input message for unrecognized entity entity=190v0 target_data.states=BEIStateSequence { start_state: ActionsSnapshot { state: None, value: Axis2D(Vec2(0.0, 0.0)), time: ActionTime { elapsed_secs: 0.0, fired_secs: 0.0 }, events: ActionEvents(0) }, diffs: [SameAsPrecedent, SameAsPrecedent, SameAsPrecedent, SameAsPrecedent] } end_tick=Tick(13694)
Ah! Interesting. These errors only occur on main not on tag 0.26.4
Hi, If I was to use lightyear with steam, what would I need to change from a default lightyear setup
Hi yes I'm still planning to, the lightyear API would be unchanged, only the replication internals would be modified!
Just add a SteamServer or SteamClient component, you can search for it in the codebasr
I can't figure out how to replicate input to other clients with leafwing. It looks like I've got the same setup as examples meaning InputPlugin has rebroadcast_inputs: true and controlling client spawns InputMap but still only the server replicates ActionState components. I've enabled debug logs and see that Rebroadcast input message... message appears so it looks like the other client should receive those...
@pine cape I've got a pretty general question, sorry if it's been asked before but I can't really find anything about it in the docs
Is predicting dynamic component insertions / removals supported? I feel like each time I remove a synced component it causes significant lag on the clients, and I can't tell whether this behavior just isn't supported or the rollbacks are caused by something else entirely (e.g. client input which leads to the component being removed).
Weirdly, refactoring that removal to simply mutate a component instead seems to stabilize things significantly. I just want to know if this behavior simply isn't fully supported or I should be debugging something else.
In previous versions of lightyear I could get a list of the entities that a ReplicationTarget is sending to through ReplicationTarget::sender. But it looks like that's been removed in 0.26. How can I go about migrating this?
Do I just need to query for all senders and match against the ReplicationMode and just handle every case?
For additional context, I want to set ComponentReplicationOverrides for my component so that it only replicates to the prediction targets. But ComponentReplicationOverrides is keyed on the sender entity, whereas the prediction target is looking for the PeerIds
Ooof actually looks like ReplicationTarget::mode is private, and ReplicationState (which is where the ::senders moved to it seems) doesn't allow you to access the senders
So it looks like this is just a regression
it is supported
Yes the list of clients was duplicated in a lot of places so i was trying to centralize everything into ReplicationState, but i might have missed a few things... I am still moving things to replicon so that might change a bit
Periwink, are you open to structural change PRs right now? I’d like to add support for GGPO style input rollback and deterministic late join but a lot of the current code is tightly coupled. ie. determinism code is coupled with prediction and interpolation, input is coupled with prediction, etc. I’m happy to make the changes but I’m not sure how you’d want to handle structural changes if you’re in the middle of porting to replicon
Hi trying to migrate my project from an older version, does anyone know how to use observers for host-client setup in latest versions (0.26.4) of lightyear? I was able to receive events for remote clients using On<RemoteEvent<MyEvent>> but it doesn't seem to trigger for the host client/RemoteId(Local(0)). I was using
ServerMultiMessageSender to trigger the event like:
message_sender.send::<MyEvent, MyChannel>(
&MyEvent(0),
server.into_inner(),
&NetworkTarget::All);
I know I could directly trigger the event for the host client, but is there a consolidated way for the server to send a bevy Event to all clients?
yep let's wait on the replicon integration. It's almost ready, the status is:
- all tests are passing
- most examples are working
Missing features:
- replicon has a single Client or Server resource, so multiple clients/servers in an app is not supported
- main blocker: replicon doesn't support having a Client and Server in the same app, so the server cannot receive replication updates from clients. BEI integration relies on the client replicating some
Actionentities to the server so this is the main issue I'm facing currently. (also the client_replication example is broken because of this) - for similar reasons, authority-related features are broken
Normally this should work, I think I had some unit tests that test exactly this scenario: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_tests/src/host_server/messages.rs#L100
I will take a look
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
It could also be something wrong with my setup, just started migrating the host server parts. edit: I needed to add the Linked component when creating the host client
I managed to get my old project updated to latest bevy/lightyear and it "works" again but something is fundamentally wrong. I'm using a dedicated server (e.g. no graphics/audio etc.) and a full game client setup. The basics work, but the client is extremely "choppy", it seems like things only get updated on the server update tick. Another odd thing is it only works at all if server is using default ticks (1/60) while the client is using 1/64. Using same value doesn't work anymore, no input gets registered
I think there's a bug with lightyear_avian where because of the ordering of systems, if a networked object moves in the same frame it spawns, a child collider will be given the wrong ColliderTransform, making the child collider basically permanently out of sync.
Based on my investigations, it looks like since this system https://github.com/avianphysics/avian/blob/3421b21b9903afdb0bfa7bb20eea05afc86a1895/src/collision/collider/collider_transform/plugin.rs#L108 runs in FixedPostUpdate, whereas the rest of the position<->transform sync happens in RunFixedMainLoop, somehow it gets out of sync.
For context, I am making a simple elevator. In FixedUpdate I change its Position to move it up or down. Because of this syncing issue, it clips into the floor (and desyncs across the network).
If I make it wait for a couple seconds before starting movement, it works perfectly fine.
My broader concern is how much of avian lightyear has to rip out and reimplement. Is this really necessary?
My understanding is this is done so lightyear can do smoothing of server corrections. But that seems like a heavy hammer to swing. Could we instead just add our interpolated server corrections before the last transform propagation, then subtract them in like PreUpdate or something?
It's possible there's more to it that I'm missing though.
Oh actually I was misunderstanding, the default for the avian plugin is to only interact with the position, and frame interpolate the position as well... Either way, applying the corrections and then reverting them after rendering still could apply?
It looks like this is sorta what AvianReplicationMode::PositionButInterpolateTransform, though it still ends up needing to reimplement some avian stuff which is sad
I used the same value for the client and server, but it looks like if the tick rate is too low inputs don't get sent. I've been trying to use a low tick rate to figure out problems with prediction/interpolation, and also ran into this issue.
In my case both the client and server are using a tick rate of 1/10 and that breaks client inputs
Also it looks like the fps example I. Lightyear doesn't use any interpolation: setting the tick rate low results in unsmooth movement
Safari iOS doesnt seem to be able to connect through WebTransport. I got it working from Android phone, but not iPhone, on latest v26.4 with WebTransport is enabled in advanced features / feature flags in iOS. Anyone know how to fix?
Do you have any idea why the choppiness though? Shouldn't replication + interpolation + prediction mean a fluid client?
That issue I described is that the client seemingly doesn't send inputs to the server if the tick rate is too low. No idea why.
Or maybe the server doesn't handle the client inputs if the server tick rate is too low.
The "problems with prediction/interpolation" is from the fact that my simulation isn't deterministic, because it's relying on Transform which is being messed with by interpolation.
(Basically I'm fighting between lightyear, avian, and bevy_ahoy all trying to mess with the Position and Transform in different ways)
ah, yeah Ican def. see the delay if I set it low for input itself ON TOP of the choppiness being terribad
this is kinda sad coz it used to work in older lightyear/bevy
lightyear's native client_config only has two paths — with_server_certificate_hashes (digest pinning, 14-day limit) or with_no_cert_validation (insecure). There's no path to with_native_certs for standard CA validation.
Any plans to change this? @pine cape
I have to renew these certs every 14 days which is causing my server downtime having to restart with new certs
Maybe something I'm missing but why don't you change the empty-digest fallback from with_server_certificate_hashes([]) to with_native_certs() in lightyear?
Hey so this is more of a general server question but since I'll be using Light-year in my project I think it's applicable. So basically I want cross platform determinism and originally I was looking into dynamic lockstep architecture and since I've been told that it's easier to achieve determinism on it and it doesn't cost a lot of server resources.
However my main concern is lag. Now I know I can use a relay server to ban connections with exceptionally high ping. But my main concern is that I fear it would region lock players from playing on other servers due to creating lag that will impact everyone.
The other option is prediction and rollback. But my main concern with it is that it will mess with the determinism and make it a pain to implement (not to mention balancing rubber banding and such).
I do know that the game will heavily involve physics interactions as well as likely being 8-16 player game (maybe more I'm still working on what that would possibly be.)
Feel free to ping me so I don't miss the answer
Determinist is basead on others things not on the Network api
The Network just send and read what You give
Like For Physics avian have Determinism feature
I seem to be having trouble mapping a reference to an Entity from the client to the server.
On the server I create an Entity with an ItemId, avian3d::Position (Both are registered under protocol.rs), and a Replicate component.
It appears on the Client as expected, but when the client targets the entity (I've inspected components and made sure its the right one) and sends a message to the server with a PlayerInteract(pub Entity) struct, the server doesn't find an Entity that contains the avian3d::Position or ItemID component.
I'm aware the client & server have differing Entity IDs, but when trying to use the EntityMap within MessageManager like so, no entity is mapped, whether using local_to_remote or remote_to_local.
let target_entity = manager.entity_mapper.local_to_remote.get(&interaction.0).expect("No entity mapped?");
Am I missing something and/or going about this the wrong way? How should I have the server & client agree on which entity they want to do work on?
I figured it out, searched through examples and used .add_map_entities() when registering the message in protocols
Is there a branch of lightyear to use with bevy 0.19-dev?
I marked my PR for the controlled fix as ready for review since someone else looked into the BEI example issue.
https://github.com/cBournhonesque/lightyear/pull/1425
Hi everyone, I need some tips
I've been trying to implement a host-client system with lightyear
All of examples I've seen on the official repo do client/server separate binaries
I basically want so that, when the game is in single player, it actually communicates to a hidden server in the background, an you can just opt to open it up for other players
So that the entire game is built on top of networking from the ground up
Can anyone point me to an example or to the right direction? Is lightyear adequate for this?
I think this would just be a HostClient configuration, and just set the "binding address" to 127.0.0.1 instead of 0.0.0.0
Builds a ServerConfig.
via https://docs.rs/lightyear/latest/lightyear/websocket/server/struct.ServerConfig.html#method.builder
Configuration for a WebSocketServer.
Thanks!
This is outdated but the general idea still works and it what I do in my bevy-lighyear game! https://github.com/SueHeir/lightyear-menu
its all one binary, but different threads and apps, which might not be what you are thinking of
You can still have prediction and rollback with determinism if you're using input-based replication
Hi, I'm having an issue with input processing on the server side (using leafwing). Sometimes the client sends and input, the server receives it (I see input buffer update in logs), but the shared system responsible for processing that input sees it only client-side. This results in a situation where client predicts a movement and moment after it is rollbacked like the input never happened. Using default InputPlugin and PredictionManager settings if that matters. Does anyone have idea what could I be doing wrong?
Hello, I got this error running the fps example
I managed to seemingly fix it by changing system ordering, but idk if I broke it because the example doesn't seem to work very well, the client host cannot shoot, and the joining client's shots are stuttery and don't register well (goes through the target then despawns), when I thought client side prediction would look better especially in a localhost scenario.
Also the avian_3d_character example seems to have the same problem where in HostClient mode, the input doesn't work
@pine cape Any plans to split the PeerId enum into variant types?
It's kindof a rough edge how I have to unwrap a PeerId as the Netcode variant everytime when it will only ever be that one type.
Hi guys
I've been having a weird issue regarding input replication to the server when running host-client mode
Has anyone faced this? When only the host is online, I can't control my character (the aircraft). The client is generating inputs, but the server doesn't replicate them and just rolls the player back.
However, it starts working when another client joins... then I can control both the host player and the joining client player.
I've tried some of the examples from the lightyear repo (avian_3d_character, simple_setup, bevy_enhanced_input) and they all face the same issue on the host-client mode.
I've used avian_3d_character for reference for my code.
Update: I actually found an issue now on github regarding that exact problem.
https://github.com/cBournhonesque/lightyear/issues/1394
Unfortunately it hasn't yet been fixed. Has anyone found any workarounds for hsot-client mode when playing alone?
@unique plover, do you know if this BEI issue has been resolved, and how? I found out it's not only the BEI example that has this issue with host-cleint, but all host-client examples I've tried
Hi guys!
I noticed that the lightyear section in Cargo.toml doesn't include a [package] section, and there's no name = “” for that section. But it is present in lightyear/lightyear/Cargo.toml.
Could someone please explain to me why it's set up this way?
sorry for necro-replying, but I was wondering whether it's possible to do p2p with lightyear in wasm, and stumbled upon this. are there any plans to introduce matchbox transport in lightyear by any chance? :)
Sorry, I didn't validate the example myself and haven't tested any of the other BEI examples. I only fixed the controlled component replication.
Don't really have a plan for this but would gladly accept a PR
I'm going to merge the replicon integration soon
It will also fix the BEI examples
Thank you anyway!
Yeah if anyone sees this, in my current project, adding a small input delay (1-3 ticks) actually fixed all issues (in the examples, the added input delay is 0). Maybe not the most optimal solution but fine for now in my case.
It feels like a race condition issue for sure
I tried replicating it in the examples, but it didn't fix them there, so something is different and I couldn't figure out what exactly it is to make a PR
If I happen to figure out the issue and it still exists by then, I will submit a PR for sure
Been loving this crate
A few versions ago I know if I added a delay of 0 it would behave weirdly, but not adding the delay config at all behaved correctly I believe. Something like that anyway
Hi, have factorio like game (but cannot go lockstep), singleplayer first. I cannot quite understand what performance cost Local connection can introduce compared to singleplayer only game. They say "No packets are actually sent over the network since the client and server share the same World." but is there any data duplication or sync via crossbeam channel happening?
There is almost no data getting sent, replication/input messages are skipped for the host client
oh, thats reassure
what cost is still present tho?
Can't say about the state right now, but after the migration to replication there shouldn't be any cost at all. The connection is emulated "logically" by simply draining server event and reemiting client events.
I explained this approach in the replicon docs if you're curious.
oh, thats really cool, thx
I opened up few PRs to fix these issues with the help of Claude Code
actually, they are not addressing host-client, only networked, sorry
I will have to probably check if any of my PRs are relevant when replicon is merged
I'm struggling with a weird problem in a host-client setup where my "ControlledBy" entity associated with a "HostClient" doesn't get a "Controlled" component..
You'll have to be on main for that fix since my PR was merged:
https://github.com/cBournhonesque/lightyear/pull/1425
Hi, how one will do main menu in which you choose singleplayer (host) or client? 2 different apps, one for menu, 2nd created depending on chosen mode?
Also i wanted to ask if there will be a significant api changes after migration to replicon
Hi there, I'm currently trying to setup avian2D with rollback and i'm having trouble setting up. Currently, the initial entity is replicated, but there is no issued rollback. The entity has the Predicted Component and has confirmed positions and history but there does not seem to be any rollback checks. As shown on the GIF i provided.
I tried using the avian physics example and added the following plugins, but I think I might be missing something?
// physics
app.add_plugins(lightyear::avian2d::plugin::LightyearAvianPlugin {
replication_mode: AvianReplicationMode::Position,
..default()
});
app.add_plugins(
PhysicsPlugins::default()
.build()
// disable the position<>transform sync plugins as it is handled by lightyear_avian
.disable::<PhysicsTransformPlugin>()
.disable::<PhysicsInterpolationPlugin>(),
)
.insert_resource(Gravity(Vec2::ZERO));
In my project setup, the toml has interpolation and prediction as such:
lightyear = { version = "0.26.4", features = ["client", "server", "netcode", "replication", "udp", "avian2d", "interpolation", "prediction"] }
Thanks in advance for anyone who has an idea of what I could have forgotten about.
There is no big API change, but some things will be deprecated (client to server replication, multiple replication senders, authority handling, delta compression) until replicon can handle these features
Did you add a PredictionManager on your client entity?
I see, thx
Ah... Yes, that would do the trick... I did not end up seeing it in the example!
Thank you so much for the help!
Got it all working to a decent degree, all thanks to your wonderful help!
jesus dude you making the golden dome?
literally looks like an iron dome simulation
😂
It's the iron dome from wish considering how bad it is currently. But yeah that is the idea.
you work in defense?
or just a fun project
This is basically something that I aim to be similar to Artemis/Emtpy Epsilon (while addressing some of the limitations I dislike).
Most of the work so far has been on lua scripting for scenarios/saving/game master actions. So nothing flashy so far, but once I tackle networking I will finally have the technical foundation to start working on gameplay.
As much as it's just a fun project but has been so for almost a decade. I intend to use that software to open some sort of space ship immersive theatre like https://bridgecommand.space/
Awesome!
Is there any better ways to do that than several apps?
You can have one app with both ClientPlugins and ServerPlugins, and only start the server if you select 'host mode' in the menu
I wish more of the examples demonstrated this, and how these components can all coexist. It's very common for games to be both host and client depending on which option you select in the menu
And I'd argue it's easier for people to remove client-only stuff after the fact to create a dedicated server, than it is to get the client+server plugins living happily in the first place
You can start all examples in host-client mode, or maybe you mean something else?
Currently most examples have a match like this https://github.com/cBournhonesque/lightyear/blob/29daa4ab5ef8135672276e967ddfd411149766b0/examples/avian_3d_character/src/main.rs#L35
That kinda implies that these plugins can't coexist, which is not at all true. Ideally the features should just change which plugin is added, and the mode should just control which components are spawned that enables the particular behavior
Some selection sharing for different game masters in what I'm creating! So far lightyear has been a breeze to work with, I really appreciate the work you guys put in it!
yes that would work
reminds me a bit of the way Beyond All Reason keeps track of all players' cameras in the replay, looks pretty cool
I might add that also.
Currently I want 2 different game master to be able to supervise a game. A bit like in a google doc where you can see the other user editing things.
is not that just host mode? how would you disable server plugins if player chose client connection?
by only start the server if you select 'host mode' in the menu you mean adding everything server related into one big systemSet and using run_if condition?
No iirc there is a start event for the server and a connect event for the client.
Those events don't need to trigger unless you want them to be running.
I assume there is also a stop/disconnect event
Yes, but server plugins (like simulation) will be running on client too, no?
If you're predicting. Likely. Which is why there are usually server, client, and shared components in examples.
Here I'm talking solely when deciding how to connect.
A host client will have the server and client component running at the same time.
What i am trying to ask is: if you have your own server only plugins you cannot add those to app before player chose host version, because you cannot remove those if player chose client version. You can guard every single system in those with one big systemSet and using run_if condition, but it feels very cumbersome and incontinent
i am not talking about shared plugins and prediction, those seems trivial
Yeah good point, I have not looked into that too much yet. But so far I have not run in the issue as most of my system are dependent on clients being connected ex client_query: Query<&RemoteId, With<ClientOf>>,
You dont disable the server plugins, you just Stop the server or don't spawn a server entity if you want to be in pure-client mode
not lightyear's server plugins, but mine
as i said there
Yeah you would have to add them all if you want to potentially support host mode. But I think now bevy has a way to remove systems/system-sets, so you could also do that.
I guess use run conditions or wait for bevy to have more dynamic plugins
i see
I upgraded my project to the newest lightyear commit and I keep getting this crash on the client-side. It seems to be triggered by joining a server or spawning predicted entities, but it will also happen randomly with no apparent cause. Anyone know what could be causing it?
It means that there is a tick missing in the mapping between replicon tick and lightyear tick, let me look into it
Some more progress on some game master tools.
I was wondering, would anyone know why despite not moving all the physics entity in Avian2D do not go into sleep and always get called back into active mode? That might lead to using a lot of resources at scale.
I think update_cursor_state_from_window in the projectile example should have With<InputMarker::<PlayerContext> when querying ActionMock. I have a similar setup, without this filter there are 2 matches for the query and constant rollbacks.
With vs without filters on ActionMock write, without filter seems to crash a lot with the ServerMutateTicks crash I mentioned earlier.
Do you have prediction enabled? does adding app.add_rollback::<Sleeping>(); help?
do you always get this error on that line of rollback.rs?
yep that sounds correct. I haven't tried to fix the projectile example since it is quite complicated. I focused mostly on the other examples
After removing all the networking plugins I still have the mistake. It feels like I did something wrong elsewhere. I will have to bisect to find what I did.
yes
I noticed I get this error to occasionally, it only happens immediately after connecting to a server
Was kinda curious if the bevy_replicon change was still in the works? I've finally debugged some stuff and I'm easily handling 100+ enemies (could probably do more just didn't test it). Is replicon known to be more optimized than lightyear? what exactly is the driving idea behind the switch? I'm asking because I'm quite happy with where lightyear is now:)
Setting ReplicationMetadata to 100ms seems to have stopped this crash
Yes it's merged in main! Yep the replication logic in replicon was more optimized than lightyear + they had a couple of features that make prediction/interpolation easier to implement
@stray sinew before that you were doing replication on every tick?
I wasn't initializing ReplicationMetadata at all in my code, so I think so?
Sg, will test in these conditions
I was kind of surprised that lightyear did not have any webrtc transport, so I decided to make it.. It kind of works!
if anyone's interested - https://github.com/strowk/lightyear-webrtc
Hey, I've been playing aroudn with lightyear recently and I have noticed a strange thing that I couldn't quite find any explanation in docs, at least not in the book (I read the whole "advanced replication" and anything that mentioned replication at all). It would appear that occasionally updates to replicated entities are missed in that client does not receive them. This only happens occasionally (maybe one in 10-20 changes), I listen for changes on client using Changed<T> and sometimes client does not seem to see a change to component T. It looks as if replication just lost that updated completely. I understand that updates and inserts are tracked separately, but nowhere in docs I saw actual statement that it is expected for updates to be completely missed in some situations.
Anyone has any idea if this is actually expected or it is more likely that I messed something up?
It seems to work as expected if I remove/reinsert instead of mutating, but this seems like very complex way of having to do this now for all changes that I need replicated or accept occasional random "it does not work this time" situation
Joining a server with a lot of entities crashes the client and causes this log spam on the server
The updates might be lost but will be resent, the model is eventual consistency, we keep resending updates until we receive an ack for it
Could you please open an issue? Must be a tiny bug in packet building inside lightyear_transport
Heyy, I’m trying to use lightyear 0.26.4 and looking at one of the examples “common” it uses a macro lightyear_debug_event!() but can’t seem to find anything about it?
This is for a custom tracing layer in lightyear_tools/src/debug.
My goal was to provide some helpers to store some structured logs in jsonl format that can then be analyzed later in duckdb or something similar
I'm having issues with replication groups. I have separate entities for the body (position) and view (camera orientation) for my player. The Player has a body_handle and a view_handle containing ids of previously spawned Entities. I'm using the MapEntity trait, but I'm getting a PLACEHOLDER on the client because the view and body entities are being replicated after the Player.
One solution is to put all of these in the same replication groups, which guarantees that these entities are replicated together.
Another is to switch to the main branch, which uses replicon as the replication layer. In replicon all entities are always replicated together
Any expectation when the replicon version will be released? (No pressure obviously)
probably at the same time as 0.19
I will release the replicon version for bevy 0.18 and bevy 0.19
Awesome! I'm interested to see this!
You probably need the change you recently submitted in 0.18? If yes, I'll draft a patch release for 0.18
Sure that would be helpful! thanks
Will draft tomorrow. Probably not as a patch since I merged https://github.com/simgine/bevy_replicon/pull/696 which adds an enum variant.
Even though it's most likely never matched by users, technically it's a breacking change.
just linking this back here, I think that it is relevant #ecs-dev message
Will there be a replacement for the old ComponentReplicationOverride to support per-component replication control?
The patch is up!
The mentioned PR will to go into Bevy 0.19 release since I already have an RC for it. But this shouldn't be a blocker for you 🙂
Yes there is still support for per component visibility.