#lightyear
1 messages Β· Page 6 of 1
Can you try using lightyear main?
One bug I fixed recently was that during rollbacks we were still buffering input messages to send
how recently?
which means that every rollback was greatly increasing the number of input packets sent
2 days ago
and you're using leafwing inputs?
yea LWIM
which commit was it?
i'm one commit behind
i'm on df475e9d0d622cf7920dd80b31e705c9bd2c76c5
df475e9d0d622cf7920dd80b31e705c9bd2c76c5
yea
Not sure; can you reproduce this on one of the examples?
yea i'll try
could it be change detection?
I'm not sure, hard to tell without more information. Do you confirm that the extra messages received are InputMessages?
yea i'm confirming that right now
so i'm sure that a large part is input messages, but it's also other replication updates
when i disable input plugin, it drops to ~1k packets
when i set the send_interval to like 10 sec
i still get a bunch of replication updates
idk i just tested send_interval = 1s in the simple_box demo and it seems to work
hmm
i'm using webtransport
that's the only difference i can think of in config
when I set the bandwidth limiter, it is properly limited
i mean i can't reproduce it on the examples at all 
i think it's very much a me issue
yea, it was the way i initialised client/server plugins
i replaced the clientconfig/serverconfig resources later
with a specific address
and it doesnt update shared config
cool
Is there a way to log the IP of clients connecting to the server?
I did a bit of digging and I can see where the ClientId -> IP mapping existing, but it's, of course, in the Netcode specifics, which is like four layers below my system that's receiving the Lightyear connection events.
I'm unfamiliar with the other transports, so perhaps the question is meaningless in their contexts, and that's why it's not easy to get at.
I don't think there's a public way to do it, It should probable get added to this resource https://github.com/cBournhonesque/lightyear/blob/main/lightyear%2Fsrc%2Fconnection%2Fserver.rs#L175
If you're connecting via steam there is no visible IP, so it can indeed vary
// Example of a component that contains an entity. // This component, when replicated, needs to have the inner entity mapped from the Server world // to the client World. // You will need to derive the MapEntities trait for the component, and register // app.add_map_entities<PlayerParent>() in your protocol #[derive(Component, Deserialize, Serialize, Clone, Debug, PartialEq)] pub struct PlayerParent(Entity); In this comment you are basically telling me that if an entity is pointing to another entity, they should always have the map_entities impl. Might I ask why? Is it because server need to find them?
Let's say you spawn two entities 1 and 2 in the client world, and you have a component PlayerParent(2) added to entity 1.
When you replicate those entities to the server, they will be assigned new entities on the server world (for exampel 36 and 37). If we just replicate the PlayerParent(2) as is, it will point to a different server entity
So the MapEntities is just to tell the server to convert PlayerParent(2) into PlayerParent(37) upon receiving the component
Okay I got it thanks
Another question lets say I have an event that sended a vec2 Should I make it into a message like MovePlayer and that moves the player in client?
I got a little confused on how I can do this
Oh wait saw an similar example here forget about it
Events that directly move the player or are input related should be handled via Inputs instead
https://cbournhonesque.github.io/lightyear/book/tutorial/build_client_server.html It seens that this documentation seens not to be connected with the current simple_box example. I am guessing you are utilizing the settings.ron file to configure the magic. Correct?
Yes
you can look at https://github.com/cBournhonesque/lightyear/tree/main/examples/simple_setup for a simple way to setup the plugins without any magic
What is your goto? I saw in your game you went for the latter
And since you are the boss oughta to follow your footsteps honhon
I'd stick to the simple version for now
Ah yes I see
No reaction, oh cmon it was midly amusing
Why I get the feeling I might regre this
@pine cape Master look at my magnificient creation
took me a while becau i am trying to follow workspace workflow since I already have a full game
ah this message is just good
awesome, glad it works!
some really intersting PRs open related to FixedUpdate and physics
currently building my stuff against it to see if it fixes an issue I have
i was also wondering if there's any known issues related to packet loss and the LWIM integration
because as soon as I use an OS level conditioner to run the avian example with 0.2 packet loss rate
lightyear seems to refuse some input packets
and it's causing the FPS to drop due to excessive rollbacks on the client side
The server is refusing input packets?
A lot of rollbacks is not surprising, the client and server state might diverge if you have this much packet loss (although normally with redundancy it should still be ok)
yea sometimes it does, when theres packet loss might just be losing the input packets though
i'm using this pattern to add VisualInterpolateStatus, checking Without<Confirmed>, but it's still adding the interp stuff to my confirmed entities on the client (which is causing some weirdness i think). Is it possible that replicated components like Position are inserted before Confirmed? and if so, can we consider that a bug?
here's how i'm adding them (basically like the spaceships example). I use Without<Confirmed> instead of With<Predicted> so that if i run the server with a gui, it also gets visual interp.
// ..
app.observe(add_visual_interpolation_components::<Position>);
app.observe(add_visual_interpolation_components::<Rotation>);
// ..
fn add_visual_interpolation_components<T: Component>(
trigger: Trigger<OnAdd, T>,
q: Query<Entity, (With<T>, Without<Confirmed>)>,
mut commands: Commands,
) {
if !q.contains(trigger.entity()) {
// not a predicted entity, so don't interp
return;
}
info!("Adding visual interp component to {:?}", trigger.entity());
commands
.entity(trigger.entity())
.insert(VisualInterpolateStatus::<T> {
trigger_change_detection: true,
..default()
});
}
I have a type alias called Displayed equal to Or<(Predicted, Interpolated)>, which wouldnβt suffer from this (and is also clearer imo)
that's a great idea thanks, i will change to something like this, to include server entities:
type RenderedEntity = Or<(With<Replicate>, Or<(With<Predicted>, With<Interpolated>)>)>;
Replicate being the component the server has, in case i want smoothing if the server has a gui
hmm, or perhaps just define RenderedEntity based on server/client features
this works nicely, thanks π
i've PRed a fix to the spaceships example in light of that
all the examples in the repo are like that
oh yeah dumb question
impl MapEntities for DockingJointMarker {
fn map_entities<M: EntityMapper>(&mut self, mapper: &mut M) {
self.entity1 = mapper.map_entity(self.entity1);
self.entity2 = mapper.map_entity(self.entity2);
}
}
this is for a server->client replicated component β what i really want is to map the server entity to the Predicted entity on the client, not the confirmed entity. is that possible somehow?
(if i understand correctly atm this will map the server's entity to the client's Confirmed entity)
What is the exact use-case?
In general the server entity needs to be mapped to the Confirmed entity, otherwise replication updates will get messed up
and then you could manually map from the Confirmed to Predicted
For example with this: https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/client/prediction/resource.rs#L49
(which reuses the map_entities function you defined for your component)
i have a system that runs on server and client to detect a landing, and dock the player to the landing zone with a fixed joint. as seen in this comment: https://github.com/Jondolf/avian/pull/507#issuecomment-2328750872
here's the relevant system bit that creates the joint, which happens by creating a (replicated) DockingJointMarker which has a hook to insert the actual FixedJoint. I generate a suitable hash so it can be predicted by clients.
// DockingJointMarker is ChannelDirection::ServerToClient
// .add_prediction(ComponentSyncMode::Once);
//
// Actually creating the FixedJoint works like this:
// You spawn an entity with the DockingJointMarker component, along with a suitable
// PreSpawnedPlayerObject.
//
// Hooks in the DockingJointMarker component will insert the actual FixedJoint and do the housekeeping.
let joint_marker = DockingJointMarker {
entity1: hit.entity,
entity2: player_entity,
rotation_offset,
local_anchor1: joint_anchor_lz,
local_anchor2: joint_anchor_player,
};
// joints are also predicted by clients, so must have a unique hash.
let joint_salt = joint_marker.hash_without_entities(player.client_id);
info!("Spawning joint with salt {joint_salt:?}");
let mut joint_entity_cmd = commands.spawn((
PreSpawnedPlayerObject::default_with_salt(joint_salt),
joint_marker,
));
if identity.is_server() {
let replicate = ...
joint_entity_cmd.insert(replicate);
}
on the server, the joint's entity1/entity2 are the (canonical, only) entities for the player and the (eg) asteroid. on the clients, the joint is created between the Predicted player and Predicted asteroid entities. but the entity mapping means DockingJointMarkers which are replicated server->client end up referencing the client's Confirmed entities.
i suppose i can spawn them on the client referencing Confirmed entities, and lookup the predicted ones in the hook when spawning the joint, so it can handle confirmed entities coming from the server. wondering if there's an existing patter or something for this kind of thing
app.register_component::<DockingJointMarker>(ChannelDirection::ServerToClient)
.add_prediction(ComponentSyncMode::Once).with_mapping_confirmed_to_predicted_entities();
the most useful api for my scenario i think would be something like that ^ which maps entities again when copying the component from confirmed to predicted entity, to translate confirmed to predicted entity ids. perhaps there's a different approach though? i can of course just do the mapping myself in the hook.
in "everything is predicted" client land i don't really ever want to refer to confirmed entities, since i'm simulating the predicted ones.
(i'm implementing it by manually mapping entities myself for now, will see if that brings enlightenment..)
got it working my using Confirmed entity ids in my marker component and translating as needed in the hooks. now clients can prespawn the same FixedJoints as the server using the PreSpawnedPlayerObject mechanism, and it syncs up. a little gnarly, but hooks came in handy. https://gist.github.com/RJ/5b803ce053f9c4b4c4726724877c4b3f
@wintry dome i'll try to take a look at this over the weekend. I didn't really plan for pre-spawned entities with components that refer other entities, cool that you got it working!
yeah it's a slightly unusual case. nice to be able to predict the joints on the clients though. if you do take a look i'd be interested if you can think of a better approach π
Question if I have two distinct packages with their own bins/mains, how can I make it so I can combine them together?
Depends on what you are trying to achieve. Just calling the two mains or what? If so, just make one of them a lib instead of a bin and have smth like an init func that you call from your other main
I was trying to follow a workspace infra, but it seens I tried to fly before I could crawl. My ideas was to dissociate server and client. Into two different packages but it seens that required more configs than I expected
I dont know why but I can for the life of me, figure out why my gizmo is not being replicated in client
u could do this with workspaces yes, i personally prefer to simply share the code in one package and conditionally compile server and client code in based on target or smth like that
@pine cape had this random idea, idk yet how feasible it is though.. what do u think of multiple protocols in one app? it seems like a neat logical feature since it would be possible eg to have different protocols for different platforms / transports or even things like a specific protocol just for "admin users".. smth like that. The question is if thats even feasible since the protocol is so tightly coupled into the app.. yea dunno
Especially inputs would be hard to do i think
actually maybe nvm that, you would just need to have different action enums for each protocol ig
so that should at best be somewhat of an annoyance
Hey, what's the correct way to handle PreSpawnedPlayerObjects that don't live long enough to receive the replicated entity from the server? E.g. a projectile that gets destroyed 1-2 ticks after creation.
Currently I have the projectile cleanup system running as a shared system, but it's causing plenty of
ERROR lightyear::shared::replication::receive: Received despawn for an entity that does not exist
If the projectile is alive long enough to start receiving updates from the server, the error does not happen
hmm, that's an interesting edge case. i've a feeling i might run into that at some point.
i don't know the answer.. could be worked around by removing the useful components and leaving PSPO and a DespawnMeIn(10: Tick) type component for cleanup i suppose
Hm is it causing some issues or is the only side-effect the extra logs?
@wintry dome so i'm trying to understand the problem with the docking.
There's a shared system which:
- creates a PreSpawned entity on client with
DockingJointMarkerwhich references the predicted entities - creates a PreSpawned entity on server with
DockingJointMarkerwhich references the server entity.
The server-entity gets replicated to the client, withDockingJointMarkerreferencing theConfirmedentities. Thanks to the hash, we find the correspondingPreSpawnedclient entity and we add aPredicted-Confirmedlink.
I don't see where the problem happens, because the pre-spawned entity on the client should have the correctDockingJointMarkerreferencing other predicted entities
Here: https://gist.github.com/RJ/5b803ce053f9c4b4c4726724877c4b3f#file-spacepit_docking_plugin-rs-L321 you should just reference the predicted entities directly, and not convert to confirmed
There is definitely something in the projectile cleanup that's causing rollbacks, but I'm not 100% sure what yet (might be unrelated to the PreSpawned entities that get deleted early)
I've experimented making the cleanup a server-only system, and then there's some projectiles that never get cleaned up in the client. Could it be that the server-replicated entity is getting matched with the wrong prespawned client entity?
Honestly I'm a bit lost at the moment π I'll start by getting some more data on what's causing the rollbacks
I'm trying to follow the book at https://cbournhonesque.github.io/lightyear/book/tutorial/build_client_server.html but I'm getting this
error[E0560]: struct `SharedConfig` has no field named `client_send_interval`
--> src/main.rs:42:9
|
42 | client_send_interval : Duration::default(),
| ^^^^^^^^^^^^^^^^^^^^ `SharedConfig` does not have this field
is the book out of date with the code?
Yes some parts of the book are outdated; you can look at these 2 examples to follow along:
yea networking/rollback issues are kind of pain to debug π
Yep π
By the way, I added rollback events in my fork some time ago so I could have some aggregate statistics on what entities and components are causing rollbacks (I couldn't find a way to do it before). I just made it into a small PR in case you'd like to upstream https://github.com/cBournhonesque/lightyear/pull/628
Thanks! I looked at it but i think it would impact parallelism of the check_rollback systems; I think it might be better to just add log there in your fork
Yeah, good point. I'll make a generic version for my fork with RollbackEvent<C>, but totally fine if you prefer to not upstream
Question how should I use the common apps from lightyear_common_examples
I am just doing a simple import from the lightyear_common_examples, and I keep getting a file error certificates.pem, specifically the big apps::new
Oh it seens it should generate new certificates, perhaps it should generate long term ones
certification need to only be valid for 14 days according to the wt spec
okey dokey
Question the workflow to load a bunch of assets should be, client loads assets. Server replicates those assets for all too see. Correct?
Server replicates some marker component. Upon receipt, the clients load the asset
it has to support the case where the client can't predict the joint, so there's no prespawned client entity. in that case, the client will receive a replicated entitiy from the server with a DockingJointMarker, containing entities mapped to confirmed entities. this should work because the on_add hook for the DockingJointMarker converts them to predicted entities to use for the FixedJoint. (since the physics joint is between Predicted entities only on the client).
So upon connection I replicate a certain marker component, told in protocol. Which will trigger the connection event, which I can utilize to load my asset?
Ah this banana is way harder than I expected
Ah I guess I need to utilize componentinsertevent
oh man the wheels in my brain are starting to turn
i added the 3 main networking scenarios and how they should work to the comments: https://gist.github.com/RJ/5b803ce053f9c4b4c4726724877c4b3f#file-spacepit_docking_plugin-rs-L25
i'll do some more network testing this week and check if the docking stuff behaves properly. i still occasionally get unexplain bouts of rollbacks I need to chase down
how well does bevy-tnua work with lightyear?
Not well, to be honest there is a lot of sub physics going on since it is a floating character controller solely based on floating. I am GUESSING tho
And if you take in consideration the amount of alignment in in the 3d avian example probably lots of conflict, but perhaps you can mess around specific configs
Okay, going back to this rollback issue, I think I've narrowed it down enough to share: I might be running into an edge case bug with PreSpawnedPlayerObjects, probably.
To summarize: my game is using projectiles that are really fast moving. There projectiles are pre-spawned by the client that shoots. Whenever the projectiles are stopped, they get deleted that frame.
There's three types of behaviors I'm seeing with this, two of which are failure points:
- If the projectiles get destroyed really early (in the video example, when I shoot the wall I'm in front of) no rollbacks are triggered but an error is printed
ERROR lightyear::shared::replication::receive: Received despawn for an entity that does not exist
This error gets printed by both clients.
- If the projectiles get destroyed late enough, there's no errors and no rollbacks (this is the case where everything works correctly!)
- There's a middle ground between the first and second case where no errors are printed but a rollback is triggered in the client that fired the shot (this should not happen) on some of the projectile components (which component this is triggered for changes per game boot) due to a missing
history_valuein the case where the confirmed entity exists
You can see all of these cases in the left side client in the video attached.
I've got a workaround (adding a delay of 30 ticks to projectile cleanup gives lightyear enough time to catch up, and fixes everything), but I thought I'd share in case anyone else runs into this
It works; there's this outdated example https://github.com/panjeet/networked_cube_test that uses avian3d+tnua3d. The one thing to keep in mind is that you need to network the GlobalTransform component, because that's what tnua is using internally I believ.
thanks
So I still don't get some things:
- A: client should spawn DockingJointMarker with predicted entities. Server spawns the DockingJointMarker which is replicated. The hashes match, the predicted-confirmed link is added
- B (client doesn't predict a joint). Server spawns the DockingJointMarker. It gets replicated with confirmed entities. A predicted version is spawned, where a
DockingJointMarkercomponent is also added. Normally this line should be converting the component from Confirmed to Predicted entities. So everything should work. Maybe there's a bug here? - C (client predicts the joint but server doesn't): at the next rollback, the client DockingJointMarker should be removed
So normally you shouldn't have to do anything and you should just use the predicted entities directly on the client; everything should be handled for you
Thanks, that's super helpful. I opened an issue: https://github.com/cBournhonesque/lightyear/issues/631
SpawnTick is a component from your protocol?
In general I think unit-tests related to pre-spawned-object need to be added
it's one of the least tested parts of the codebase
@wintry dome I added a test that shows that the mapping from confirmed to predicted should work fine in situation B: https://github.com/cBournhonesque/lightyear/pull/632/files#diff-7f102ef336b4df8b3d69cab1be89802ada1c45b43ce94d13f243cf9d991e379cR526
Yea, SpawnTick is one of the projectile components in the protocol
shortly after this, I added a console command to test authority reassignment. I can confirm that the server correctly recognises the authority transfer, and so do the clients (their HasAuthority components shift around), but the Confirmed and Replicate components on each client don't line up. to clarify:
- Client1 spawns a vehicle and replicates it to the server. The server replicates it to Client2, ensuring that Client1 has authority over the vehicle.
a. Client1 has one entity for the vehicle. ItHasAuthority, it hasReplicate, it does not haveConfirmed.
b. Client2 has two entities for the vehicle. Neither entity hasReplicateorHasAuthority. One entity hasConfirmed. This entity is what's rendered. - So far, so good. On the server, I then transfer authority of the vehicle to Client2. This is where things start to break down:
a. Client1 still has one entity for the vehicle. It does not haveHasAuthority, but it still hasReplicateand it doesn't haveConfirmed. That is, it knows it doesn't have authority, but it's not receiving sync.
b. Client2 still has two entities for the vehicle. Neither entity hasReplicate. The first entityHasAuthorityandConfirmed. The second entity has neither. That is, it knows it has authority, but it doesn't appear to be sending sync. - This leads to an inconsistent state where neither client is sending or receiving sync, as far as I can tell.
the questions I have are:
- should the
ConfirmedandReplicatecomponents on each client be automatically updated when authority transfer occurs? - should a second entity be spawned when a client goes from sending to receiving?
- is the root issue that the second client isn't sending sync, so the first client doesn't know it should be receiving sync?
(an immediate answer isn't necessary - I'm working right now - but I figured I'd write this up now so that you can answer at your own convenience)
- b. How come client2 has 2 entities for the vehicle? One is
Confirmed, one isInterpolated? TheInterpolatedone is rendered? - a. That's good. Client should still keep the
Replicatebundle, but it's not sending sync updates because it doesn't have authority.
should the Confirmed and Replicate components on each client be automatically updated when authority transfer occurs?
Replicate is not replicated on design, because you might not want to have the same replication settings when transferring authority. (especially when transferring from server to client).
You should instead add the Replicate bundle on client 2; it won't send replication updates until you first get authority.
Confirmed isn't updated at all either, currently. This requires deeper thinking, but Confirmed is usually just a marker on the client to distinguish between an Interpolated/Predicted entity and the entity that is actually receiving replication updates (Confirmed).
I assume on your server Replicate you have something like SyncTarget.interpolated = NetworkTarget::All? Then when client 2 has authority, the client 1 should spawn a second entity with Interpolated.
should a second entity be spawned when a client goes from sending to receiving?
No, if you're not doing prediction/interpolation; you should have only 1 entity per peer at all times.
is the root issue that the second client isn't sending sync, so the first client doesn't know it should be receiving sync?
Yes the root cause is that the second client doesn't haveReplicate, so the server isn't receiving any updates from client 2 that it can propagate to client 1. You can see in this example that all clients haveReplicate
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
thanks for looking into this. i just realised i need to call .add_map_entities(); when registering the component π€¦ now that i've added that, the mappings all make sense. whoops π¦
that's good, your code should be much easier now π
yeah i removed the now useless manual mapping, simplifies things a bit
thanks! I'll take a look in a few hours and get back to you with more information π
Anyone knows if lightyear has good compat with rapier?
I haven't tried it
i expect managing physics rollback will be much harder with rapier, since it doesn't use the ecs for storage of physics values like avian does
or perhaps easier if you can cheaply clone the physics world π€
@summer finch i have some rollback stuff to debug, what state is your rollback metrics thing in? i like the look of the egui pane showing aggregate stats from your video, would love to give it a try
Feel free to make a PR, I (or others) will review it when I can
gave it a shot, question why is the lobby example based around bevy egui? And not native bevy ui ,is it because of the upcoming refactor?
https://github.com/cBournhonesque/lightyear/issues/635 Made an issue if okayed, gonna pr adjustment
- Yep, that's right. I was initially rendering
Confirmed, but I've switched it over toInterpolated
2.a. gotcha
You should instead add the Replicate bundle on client 2; it won't send replication updates until you first get authority.
alrighty, I've added a system that attachesReplicateon the addition ofHasAuthority. that seems to work - the server can now see the synced data from Client 2.
acknowledged on the other points, that all makes sense.
the issues I'm seeing now are:
-
I have a server system that attaches the
Replicatebundle whenReplicatedchanges; however,Replicateddoesn't appear to change when authority is transferred and the new peer is sending sync (I can confirm that the sync is now coming from client 2); this means that the newReplicatebundle is never attached, which is probably why client 1 isn't seeing sync -
Client 2 has two entities, as expected; its
ConfirmedandInterpolatedentities. my rendering* is done against theInterpolatedentity when it doesn't have authority; when it does have authority, though,HasAuthorityis attached to theConfirmedentity, which means both entities end up being rendered* (one hasHasAuthority, the other hasInterpolated).I believe this is part of why I was using
Confirmedto begin with, so that there's only ever one entity that gets considered for tx/rx sync. I'm not sure how to best deal with this while still using interpolation; I can disable it, and that's probably fine for my application (I'm using physics as a form of interpolation), but it'd be nice to keep it working if possible
*: "rendering" isn't quite what I'm doing; I'm attaching a custom representation if it's Interpolated or HasAuthority, and it's non-trivial to transfer that representation between entities
P.S. no stress on this issue - I recognise it's the interaction between at least three complex features (client spawning, authority transfer, interpolation), and that my setup is pretty convoluted by itself
uh two question regarding interest management:
-
apparently when using interest management, when despawning entities the server asks the client to despawn entities that arent replicated to it (they arent in the same room). i think this only happens after they have been added to the room once, anyone had this issue before?
-
any solutions to having interest management in host server mode? my best guess would be using the clientside replicated marker component (nvm that, using With<Replicated> doesnt seem to work)
-
You mean that client 1 joins room A and then exits it.
When an entity E1 in room A gets despawned, a despawn message is sent to client 1, even though it shouldn't be the case? -
You mean how to restrict the visibility of the host, who can currently see everything?
- no, client 1 joins room A, entity x spawns and joins room A -> x leaves room A -> x despawns serverside -> server tells client 1 to despawn x despite x not being available to client 1
- yes essentially, if possible it would be nice to have some kind of marker that the "virtual client" would get replication updates for said entity if it wasnt in host server mode
otherwise i guess i could handle that by having some kind of marker that i add whenever making an entity "visible" for the virtual client
*my despawn logic is entirely serverside using EntityCommands::despawn_recusive
well when x leaves room A, the server sends a Despawn to client 1, so I think that's intended. Maybe i misunderstood the exact issue
Yea it's not really a priority for me right now, but it could be useful. A workaround could be to just run in client-and-server mode (2 separate threads)
Yes, but once the server actually despawns x, it will send another despawn to the client despite x not being in a room with the client
sounds like a bug to me
yea i guess workarounds exist, i just wanted to know if that was already a thing that i just wasnt aware of
I see, yea definitely a bug
It's in a good state for general debugging use I think. The aggregate metrics code is in my lightyear fork in the mbc/rollback-events (I think) branch. The egui code is in a private repo (not for any reason, I just never got around to open sourcing it). I can share later today after work hours
that would be great, thanks π
Is it possible to reflect replicated resources?
You can just enable reflection on the resource when you define the type
Alright, here's the links. It's quite heavyweight to use since I never really planned to share this, but i'll explain as best I can
- https://github.com/mbrea-c/bag_of_holding: Has a couple of helper crates for working with lightyear:
ZusammenPluginI found myself repeatedly writing*Protocol,*Server,*Clientand*Sharedvariants of plugins with similar configurations for using with lightyear, so I made this meta-plugin crate that encapsulates it in a single struct (and that way I only need to configure it once)ZusammenAppframework for running a multiplayer app given aZusammenPluginencapsulating the game logic and some basic configuration. I built this to abstract away some lightyear setup boilerplate, at the cost of making assumptions about the app (It doesn't support HostServer mode for example, as I don't really use it). Heavily adapted from the common part of the lightyear examplesbuzzdebugDebugging framework for lightyear using egui. You implementLocalDebugModulefor a module that collects data from the client and displays it in the Buzzdebug egui window with the provided render function.RemoteDebugModuleworks similarly except the systems that collects the debug data runs in the server and the collected resource is replicated to the client, where it is displayed in the debug window (yes I got tired of getting confused about what was going on in the server). ADebugZusammenPluginis provided that takes a list ofLocalDebugModuleandRemoteDebugModuleand sets up all systems, replication, etc.
- https://github.com/mbrea-c/lightyear/tree/mbc/rollback_event: My lightyear fork with the
RollbackEventand rollback cause aggregation stuff. - Attached to this message is the lightyear
LocalDebugModulethat prints out to egui the rollback cause aggregation data (it's from another private repo)
So, there's two ways you can use this:
- Option 1, ignore all that nonsense and just use the lightyear fork. The AggregatedRollbackCauses resource contains all the aggregated data you need, you can print/render it out however you want.
- Option 2, create a
DebugZusammenPluginwith the lightyear debug module attachedand run thelet debug_plugin = DebugZusammenPlugin::new().with_local("Lightyear (client)", ClientLightyearDebugModule);.add_protocol,.add_shared,.add_server,.add_clientin the Protocol, Shared, Server and Client plugins respectively. E.g.:impl Plugin for ProtocolPlugin { fn build(&self, app: &mut App) { // ...stuff let debug_plugin = DebugZusammenPlugin::new().with_local("Lightyear (client)", ClientLightyearDebugModule); debug_plugin.add_protocol(app); } }
A networking library to make multiplayer games for the Bevy game engine - mbrea-c/lightyear
here's how one would create a multiplayer app with the zusammen app
thingy, if anyone's curious (maybe I'm just showing off a bit):
fn main() {
let debug_plugin =
DebugZusammenPlugin::new().with_local("Lightyear (client)", ClientLightyearDebugModule);
let plugin = CombinedPlugins::new()
.and(CharacterZusammenPlugin)
.and(debug_plugin)
// add more zusammen plugins here with actual game logic :)
;
run_multiplayer_app(ZusammenAppConfig {
plugin: Arc::new(plugin),
mode: ZusammenAppMode::Host { port: 1337 },
})
}
And yeah the naming scheme is... they were never meant to see the light of day
After writing this up I realize it's probably a lot of friction to set up, probably taking option 1 and just logging the aggregated rollback causes will be faster for some quick debugging
poggerrs
@pine cape Btw I am finishing up the whole pretty lobby ui system of my game thankerino for u examplerino
by the way, on the subject of interest management: my plan is to calculate what each player can see on each interest-management-tick by using a KD-tree to locate nearby entities.
what's the best way to do this? do I manually maintain the entities that they can currently see so that I can lose_relevance for them once they leave, or can I directly control the set of visible entities? (I'd prefer to do this if possible - I suspect it'd be a lot cheaper than keeping track of the state myself and issuing potentially many calls to the *_relevance functions)
amazing, thanks for posting this. will be digging in properly tomorrow π
Use rooms :)
You can get the set of entities in a room and remove the ones that arent available anymore while adding the ones that are now available
There's this struct that contains for a given entity the list of clients that can see the entity, but I don't think I have the reverse (for a given client, get the list of entities that are visible for that client).
The main reason is that the replication loop goes through each individual entity that has ReplicationTarget and then uses that data to decide which clients to replicate to. Maybe there's a way to change the logic there..
So unfortunately I think calling *_relevance might be your best bet right now? If I understand correctly, it is inefficient because you would have to do a range-search every tick and then compare with your previous value to find out the points that gained/lost relevance?
I guess you could use a room to store the list of entities visible to a given client, but then you'd have to do previous_entities_in_room - entities_found_in_range_search to get the list of entities to call lose_relevance for? That seems expensive as well
that's definitely a big issue
uh idk, i just did it like this:
fn system_update_camera_views(
q_objects: Query<(), With<ObservableMarker>>,
q_transform: Query<&GlobalTransform>,
mut q_camera: Query<(&ViewRange, &GlobalTransform, &ObservationRoom, &mut LinearVelocity, Option<&ObservationAnchor>)>,
mut room_manager: ResMut<RoomManager>,
intervals: Res<Intervals>,
) {
for (view_range, cam_transform, cam_room, mut lin_vel, anchor) in q_camera.iter_mut() {
if let Some(anchor) = anchor {
let transform = q_transform.get(anchor.entity.entity()).unwrap();
lin_vel.0 = cam_transform.translation().truncate() * -0.1 + transform.translation().truncate() * 0.1;
}
let cam_aabb = Aabb::from_half_extents(
cam_transform.translation().truncate().into(),
Vector::new(1920.0 / 1.5 / view_range.0, 1080.0 / 1.5 / view_range.0),
);
let mut viewed = find_intersections(&intervals, &cam_aabb);
// TODO check for Option<&Opacity> > Some(0.0) || 1.0
viewed.retain(|e| q_objects.contains(*e));
let prev_viewed = room_manager.room(cam_room.0).entities.clone();
prev_viewed
.difference(&viewed)
.for_each(|x| room_manager.remove_entity(*x, cam_room.0));
viewed
.difference(&prev_viewed)
.for_each(|x| room_manager.add_entity(*x, cam_room.0));
}
}
ignore the specifics to my app obv
I dont use relevance management at all
yup, that's my concern - I'd have to track which entities my players could already see and compare them to the new set, and then call *_relevance, which seems like it'd be duplicating state that Lightyear already has.
that being said, if Lightyear's internal implementation is actually "inverted" - that is, the entities keep track of which players see them, instead of the other way around - I can understand why the relevance functions make sense, and I'll go ahead with that plan for now
rooms could work - that's what I was initially considering when speccing this out a few months ago - but I'd like to also do virtual worlds on top of distance-based interest management in the future, which might not work super well
@summer finch extended your rollback cause stuff to also trigger an event on entities that cause rollbacks, which i observe in my renderer plugin to add visual indicators:
pub fn aggregate_rollbacks<C: Component>(
mut causes: ResMut<AggregatedRollbackCauses>,
mut evr_rollback_event: EventReader<RollbackEvent<C>>,
tick_manager: bevy::prelude::Res<crate::prelude::TickManager>,
mut commands: bevy::prelude::Commands,
) {
for ev in evr_rollback_event.read() {
causes.new_rollback::<C>(ev);
// trigger an event on the entity that caused the rollback
let caused = CausedRollback {
tick: tick_manager.tick(),
component: type_name::<C>().to_string(),
reason: ev.reason,
};
commands.trigger_targets(caused, [ev.entity]);
}
}
That's very nice, thanks for sharing π I'll definitely be using that too
I'll add it to my fork later today
rough and ready, but here's the renderer: https://gist.github.com/RJ/c02f0f15516b9a75d96ef2f802c58557
this is a minor thing but I wonder if we could automatically generate protocol ids to a certain extent
basically generate a hash of all registered components/their configurations
You generally wouldnt want to send every component though
ya, but do non-sent components really matter for a protocol id?
o, I don't mean register_type components, I mean specifically registered for replication
oh sorry i overread the "id" part lmao
i mean yea i guess you could do that but that would require components to implement hash, no?
no no, you can do it on TypeId I believe
only the configuration of it would require hashing
im not super familiar with the internals but stuff like ChannelDirection and stuff
but typeid isnt consistent across compilations iirc?
Is TypeId guaranteed to be identical on separate compilations, but on the same rustc version?
yeah I'm pretty familiar with that, when I made my own networking library a while back I ended up making a file that created consistent type ids for each registered component
:o
wdym 'abuse' π its for a good purpose
technically type_name is also not supposed to be used like this according the rust team
they make no guarantees
but bevy reflect stuff just decided "naw fuck that"
i mean yea but what should happen? a name is a name, its not like the compiler would randomly decide to rename a struct or smth
so far it hasn't changed but its just the lack of standardization so bevy does it anyways
I think what would happen is stuff like trimming the name or changing the path of it
not necessarily the end type name
so over time it might not match up exactly
That should only happen across compiler versions, not compilations tho
if anything
so it wouldnt be a major issue i think
ya it shouldn't be
any change they make probably wouldn't break using it for protocol ids anyways, worst case is the protocol id changes unnecessarily
relying on hard coded names is obv not a good idea tho xd
unless you get quirky with it and start randomly naming things
i wonder how they are gonna deal with that once bsn etc are out
relying on reflection for that is prob not a good idea

eh, reflection is fine for it I think
not the best, but if you are reading these files from disk that performance hit will be the least of your concerns
Is there any reconnection handling? Like a way to erase sent parts of the world to a specific client
I think when a user disconnect I despawn every replicated entity
hmm lemme try some stuff to verify but i think the client didn't receive older updates if the app was restarted
webtransport and websockets have smth like built in reconnecting
steam probably too
You know what would be cool a matchmaking style example
isnt the lobby example basically that?
kinda of, but like is more how to self host and create client side lobbies
Matchmaking is more of the idea of click button to matchmake -> create server sided lobby -> connect available clients
Is simplerish and is the goto of most competitive games
hm ok
Also there is the idea of a server db, that stores character points and suchh
oh yea showing interactions with db / config crates would be cool
tho ig it wouldnt be much different from how regular bevy does it
How can I selectively transition the state of specific clients ids? Is worth noting my host will always be my server
Like only client x and y, inside lobby should have their state converted to in_game
What should I do to postpone starting replication to new clients (let's say we download a rather heavy map from URL provided by server and don't want anything weird happening on client while we're loading)? Is creating a room for "ready" players the only way? Can I exclude a specific client from recieving any messages on given replication group(s) without that?
Yes there is a function send_message_to_target where you can select the clients you want to send a message to
And for component replication you can update the ReplicationTarget component
I mean I shouldn't really modify (can I even?) ReplicationTarget of all my replicated entities on the server just because someone attempted to join (connected and sent server a message "hey! what's the current map?")
Yes maybe the best is through InterestManagement to control who is receiving replication updates
Hi, I'm following the guide to set up lightyear, and while it is a bit outdated I managed to work around that, but the guide suggests using a link conditioner, presumably to ensure the game works well under more realistic conditions, and I kept that on, but I kept seeing a weird delay even after enabling prediction, and I managed to track it down to being caused by the link conditioner's incoming_latency parameter. It doesn't seem right to me that this should affect the input delay for a client's own predicted entity, so have I set up prediction incorrectly, or is there something wrong in the way the link conditioner works?
Also it seems to be affected by both the client and server link conditioner settings, and more strangely is that the prediction takes N ms to start (where N is the sum of the delay from the client and server), but after that initial period the prediction is very snappy, and responds to my inputs immediately
@pine cape Check this out boss it working my server administrated matchmaking
I think you might have setup prediction incorrectly?
All the examples work basically instantly with any amount of latency added via the conditioner (i.e. the character responds to the inputs immediately)
Alright, I'll try running the example directly
gg, all in one app or multiple apps / ports / servers?
@pine cape ive been looking into WebRTC / p2p in general in the past few days. Given lightyear moving away from serverauthorative a bit (ie. Authority transfer, Steam Sockets, ..) is that something worth looking into for implementation in lightyear or just a waste of time (incase you don't like the idea of having p2p capabilities in the first place)?
I think from the technical side its not even that difficult to implement given that there are existing implementations of a similar style (naia, libp2p, webrtc-rs, ..)
Alright, I've got the example down, and it does work as expected, I did find that I was missing the ControlledBy, but it's still not working as expected on my own version, so I'll keep comparing them π
it generates sub server after 10 players
is the maximum my raspberry server can handle π
If you are trying to implement responsive character movement you should look at clientside prediction too, dont want to let your server hold you down
Yeah, I've got prediction and interpolation set up in the same way, it's just not working as it should (but only in my version). I'll keep looking at it though, I probably just missed something silly
Okay I fixed it, turns out it was just a bevy system order problem π ControlledBy didn't fix it, it was buffer_input.in_set(InputSystemSet::BufferInputs)
Turns out it was all due to me reading the guide incorrectly π
What's the accepted way of hiding the Confirmed objects when predicting or interpolating? It seems that when the object is initially replicated there is no Confirmed on it, so I can't check for something like
confirmed
.map(|confirmed| !(confirmed.predicted.is_some() || confirmed.predicted.is_some()))
.unwrap_or(true)
I tried searching, but I can't find anything
Only ever render things that either have a Predicted component or Interpolated component. That way thereβs no need to hide anything
Oh, but what about things that aren't predicted or interpolated? Or are all entities one or the other?
If itβs replicated it should be one or the other
But some objects are neither replicated or interpolated (not that I have any rn, but in theory some objects are)
But I'll do that for now, thanks!
any clue here why when i speed up my rocketship and get far away from the star cluster at 0,0,0 it seems laggy? any advice on things i can check to figure out what the issue is? if I drop the star field it consistently holds 60 fps but i still get laggy behavior the further away i move from the center. I can assure that the follow_camera and player movement system is in the same schedule, FixedUpdate.
not sure what it could be. I can see similar lag on the server. so not sure if it could be fps related really.
Linear andAngular velocity together with position and rotation all have "ChannelDirection::ServerToClient" and "ComponentSyncMode::Full"
Just how far (in float coordinates) do you get?
What do you have in mind? Since any player can act as a host, it's similar to p2p no?
Indeed. Thats basically what i had in mind. Actually having "p2p" would mean a change in transports tho since you dont really have a server as a relay but are directly connecting them.
idt the surface level api would change at all, it would basically just be the transportation layer getting a new option
How would the new option looked like compared to host-server mode? I think most of the lightyear replication logic needs a server
Did you enable VisualInterpolation?
https://cbournhonesque.github.io/lightyear/book/concepts/advanced_replication/visual_interpolation.html
https://github.com/cBournhonesque/lightyear/blob/main/examples/spaceships/src/renderer.rs#L69
You would have the server to be a peer. That way you could have host-server mode too
correct this is enabled. im using smooth-bevy-cameras crate to have the camera that follows the spaceship on a LookTransform component for a smoother camera. is it possible i need to add the LookTransform to component in componentregistration and also give it a visualinterpolation component?
since its moving based on position
seems to be no limit. it can go to 10000, 0, 10000 if i like.
No i don't think you need to register LookTransform.
But maybe check the ordering of your system with respect to the visual interpolation systems
my bad for not trying more different things on the camera i had. actually i think i figured it out now. the lag_weight on my smoother camera was at 0.9. when i put it to 0.01 there is minimal lag. might make sense with it seeming laggy the higher the velocity got. the remaining lag now seems to be fps related cuz when i move out of the star field fps improves, goes from 25-30 to 38-42. but thats something i just gotta work on. the stars are each a entity. gonna try and see if i can replace them with a background instead.
actually scratch what i just said. didnt make any sense anyways. iΒ΄ll have to debug some more. will take a look at the ordering of my systems.
maybe i need to do extrapolation instead? asked AI using the files as context, and this is what its saying:
" The shaking of the spaceship when it picks up speed is likely due to a mismatch between the physics simulation, network replication, and visual interpolation. Here are a few reasons this might be happening and some suggestions to fix it:
- Interpolation vs. Extrapolation:
The VisualInterpolationPlugin you're using is designed for interpolation between known states. When an object is accelerating, interpolation can cause it to lag behind its true position.
Fix: Consider using extrapolation for fast-moving objects. You might need to implement a custom extrapolation system or modify the existing interpolation system to include velocity in its calculations."
something i did to test if its the follow_camera or not thats "lagging behind" is to take it completely off schedule and zoom out. the shaking seems to go away. so probably need to put follow_camera closer to the action state/player_movement, if that makes sense.
Hold on a minute, if bevy runs all systems inside a tuple in parallel, the executions might seem off at times. Maybe adding a .after condition helps. Iβll try that when Iβm back on my laptop
add_system(handle_input) .add_system(camera_track_player.after(handle_input))
Or
.add_systems((handle_input,camera_track_player).chain())
Perhaps instead only run the system when Position is updated, using Updated<Position> etc
Made a nice little renaming thing for predicted/interpolated entities
does an entity with both interpolation and prediction even make much sense?
since prediction/interpolation are both just for rendering as far as I can tell
I don't think that should happen
Boss I am sorry to bother, but save files should be set where in server or client?
I need to save the current character loadout, I am guessing it should be in server
That should be independent from lightyear no? I think you could do either client or server
Client if you can, no need to waste bandwidth
Well it all just depends on authority there too. Do you want the player to be able to say their loadout is whatever they want?
if its some innocuous stuff you just send to the server anyways as a "loadout config" its fine on client, if its a small game that you dont care about cheating then client is also fine there. Main thing is just if there is something in the loadout you don't want the player able to do
Maybe i misunderstood, i thought he wanted to load some kind of player model
well character loadout to me implies more of a thing like abilities/inventory/gear/etc.
@pine cape possible optimization for channels according to benchmarks
https://github.com/fereidani/kanal
Thanks, would be good to benchmark these, I don't think channels are the bottleneck though
not really yea but its always good to be faster :p
They published some benchmarks which im just gonna assume to be ok
@pine finch did you have a question? :p
Is there a way to 'suspend' replication of an entity for individual clients?
I've got a scene with 10,000 entities doing their own thing split into rooms based on what's in a client's frustum and entity commands to add/remove entities seem to be very expensive (at least bandwidth wise) so I wonder if there's a way to pause component updates up to a certain point rather than outright removing the client from the room thus deleting the remote entity.
Soz for earlier, hit enter prematurely :>
I don't think this is possible now. You can suspend replication of an entity to all clients, but not to an individual client
But yea that seems like something that could be useful; some kind of configuration in the visibility handling, so that when the entity stops being visible, we stop sending updates instead of despawning/respawning the entity
I created an issue: https://github.com/cBournhonesque/lightyear/issues/651
I also assume that adding and removing replicated marker components too often is a bad idea :p
Used to add entity states with marker components but I was getting humongous bandwidth spikes so I switched to an enum and everything smooth now
You shouldn't have to add/remove the Replicated component, that's mostly for lightyear internals
you can remove/add the Replicating component to pause/resume replication
Ah, I meant I was adding and removing a component that I registered (eg. EntityTarget(Vec2)) to define a movement target for the entity
ah i see
(It probably shouldn't have been included in the protocol anyway, but I was thinking of using it for interpolation later)
Just wanted to comment back on this in case others encounter similar issues. My low key suspicion was correct. The issue is that the smooth_bevy_camera crate with the LookTransformPlugin is running in the Update schedule, so when I use the component and the plugin, the system for its component is running in Update, not FixedUpdate schedule like I actually wanted my fn camera_follow system to do. Removing the LookTransform component entirely from my camera set fixed most of the issue for me. Most of the jittery and stuttery has now gone away. Fantastic! Figuring this out might have given me the lesson I needed on schedules, sets and conditions that I need to tackle similar issues. Iβll write a PR or an issue for the crate mentioned above explaining the need to disable it from the update schedule and adding it to the correct schedule if itβs different from the one one would put physics and movement in. Basically the same thing thatβs done in the spaceships example in the src/shared.rs file , where SyncPlugin disabled from fixedupdate and instead moved to PostUpdate schedule. Gotta make sure the stuff youβre adding and running from another plugin isnβt stuck in another schedule in the background.
I noticed a common use case that requires writing lots of boilerplate that lightyear can potentially abstract over.
There are some components that can't and probably shouldn't be replicated (Collider) being one example. Atm, you have to attach a marker, detect when its replicated on the client, and add the same components again.
I am proposing maybe theres some #[derive(NetBundle)] or something equivalent which lets you have different components auto-initialized on replication by some trait, say default
#[derive(NetBundle)]
struct PlayerBundle {
id: PlayerId,
color: PlayerColor,
#[client_init(create_player_collider)]
collider: MyCollider
}
Perhaps something like this? Where you spawn the bundle on the server, and once its replicated to the client the client will use that function to initialize it?
or maybe some abstraction over hooks makes more sense
So when you detect that a PlayerId marker component has been replicated to the client, you add the MyCollider component on the client?
I think it might make more sense for users to provide their own observe/hooks/systems to do that kind of stuff
fn listener_search_match(
mut events: EventReader<MessageEvent<SearchMatch>>,
player_ids: Query<(Entity, &PlayerId), With<PlayerId>>,
mut online_state: Query<&mut PlayerStateConnection>,
) {
for event in events.read() {
let client_id = event.context();
let id_to_compare = PlayerId(*client_id);
for (entity, player_id) in player_ids.iter() {
if *player_id == id_to_compare {
info!("This player is searching for match {}", client_id);
let mut on_state = online_state.get_mut(entity).expect("I a mtired boss");
*on_state = PlayerStateConnection {
online: true,
searching: true,
in_game: false,
}
}
}
}
}```
Does anyone know a better way of changing components when a server receives a message? Like an alternative in each i dont need to iter through basically all players.
you could store a hashmap of that in a resource or smth
but what would be the key to that hashmap?
oh god nulled
why is my brain like htis
Clientid
Hey all, are there any known bugs with the LeafwingInputPlugin?
2024-09-25T02:22:50.559458Z ERROR lightyear::connection::netcode::client: client ignored packet: invalid packet: sequence 88203 already received
whenever I add the input map, I get this invalid packet spam
and sometimes the server crashes too
this happens even if I dont use the inputs in a system
That is the replay protection system, where I check if a given packet was already received. Are you testing with very high latency?
No like 80ms in the conditioner, but I tried without the conditioner and still had issues. This is a localhost connection.
If I remove InputMap. everything works fine but as soon as I add that to the player entity the replay protection system spams
if I use another transport like wasm + websockets, I dont get that replay protection spam but I do get the same broken physics before
even if I dont use the input map in any systems, adding it breaks my cube and it gets stuck floating in the air
I think there must be some other issues beside leafwing that's causing your problems; the leafwing plugin is pretty well tested
What was the input config you were using?
the default
I just can't come up with an explanation for why physics breaks as soon as I add the input map to the entity, even if I dont send any inputs
Hey all, I am having trouble adding a Scene to a replicated a character from server to clients:
- clients connect to host server
- server spawns in an entity for each client (including self)
- each entity has a player id component, SpatialBundle, Replicate, PrePredicted
- this seems to work fine
- client has a system that queries for
Added<PlayerId>, and inserts aHandle<Scene>to that entity, which is from a gltf that contains the mesh, armature, animations, etc.
- On the host client, this works fine, and I can see one character model for each client, and move them around, etc.
- but on the client, I see a deformed mess of the model, and the transform doesn't seem to be replicated properly. There are also ~350 entities spawned in on the non-host client, most of them just have an
Interpolatedcomponent.
Right now my protocol only has PlayerId and Transform copied ServerToClient.
Has anyone worked with Scenes in this way before? I couldn't find an example that uses them instead of Pbr for example. Thanks!
Hmm, I'm now also adding an entire SpatialBundle to the client models, instead of just Transform. That seems to fix the visual of the model. I am concerned still that there are ~350 entities spawned only on the non-host client though. Those only appear after I add the Handle<Scene> to the models on the host server I think. Is that normal to have so many entities with just an Interpolated component? They don't exist on the host client, so I don't understand what they are interpolating
not sure exactly what the issue was, but I fixed it by adding the bevy features:
bevy = { version = "0.14", features = [
"serialize",
"bevy_state",
] }
good to know!
Do you have code that I could look at? I would first try to get it working without PrePredicted and without host-server mode. Both add extra complexity and potential failure modes.
Try to make it work with:
- server spawns the entities and replicates to clients
- clients add the scene on the
InterpolatedorPredictedentity ifPlayerIdis received
I'll try to simplify it in those ways when I have a chance later. Thanks!
I'll share some code if I'm still stuck
Do I understand correctly that currently lightyear transfers all entity changes to e.g. position over the network?
is there a feature to enable desync detection with e.g. hashing the data and then only send entity updates when necessary to recover from a desync?
Lightyear automatically fixes desyncs
it essentially uses a lightweight version of tcp over udp (look up netcode standard if u wanna know more)
I'm wondering because the avian_3d_character example transfers several KB/s, even when no inputs are pressed
you mean clientbound or serverbound?
havent looked at that particular example but i could imagine that it transfers replication authority over the character to the client
this is with cargo run -- client-and-server
If thats serverbound then its likely clientauthorative replication
looking at RUST_LOG="lightyear::client::connection=trace" cargo run -- client-and-server and logging the decoded messages in src/client/connection.rs:435 I can see that the client receives a lot of messages of this form:
Got EntityUpdatesMessage updates=EntityUpdatesMessage { group_id: ReplicationGroupId(1), last_action_tick: Some(Tick(32)), updates: [(Entity { index: 19, generation: 1 }, [b"\x05\x02\0\0\0\0\0\0\0\xda\xf6\x9c4", b"\x06\0\0\0\0\0\0\0\0\0\0\0\0", b"\x07\x9d\x90\0@\x90\xff\x9f?7\x8cl5", b"\x08\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x80?", b"\x0f\0\x02\x01\0\0\x03\x03\x03\0\0\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"])] }
I assume this directly updates some data related to the entity. However, if the client correctly predicted the result, then this message should not be necessary, no? Thus, I suppose either lightyear always sends these fairly large EntityUpdateMessages (regardless of whether a desync happened or not) or the client mispredicts a lot
When I removed PrePredicted, all those ~350 entities on the non-host client went away. After some reading, it seems I might have been using PrePredicted wrong. correct me if I'm wrong, but PrePredicted should only be put on entities created by the client
You still need to send every server updates to the client regularly. The client uses those updates to detect if there was a desync/misprediction and issue a correction.
You can lower the frequency of replication updates if you want to save bandwidth
Yes, PrePredicted should only be added on client entities. The goal of PrePredicted is to spawn an entity on the client's predicted timeline that will eventually be owned by the server. I wouldn't worry about it too much at this stage
This does seem to have a significant impact on the data being transmitted, however there doesn't appear to be any desync detection - if the server spawns an object it will only be replicated at the next replication interval, not before.
I think I would want to be able to disable periodic updates, and only send updates when necessary.
Further, increasing the replication interval it becomes increasingly obvious that the server's and client's physics simulation are diverging, and it looks more and more glitchy/twitchy. It's especially noticeable when jumping on top of a block.
Do you have prediction enabled? There might be a misunderstanding here; you can try setting the replication_interval to 10s in the simple_box example, and the client's cube will still remain completely smooth. That's because we apply the local client inputs immediately, and possibly do corrections only when the server updates reach the client. (And there shouldn't be any corrections if you only have 1 player)
Desync detection is usually used if you're only replicating inputs (as opposed to what lightyear is currently doing, which is replicating the state of the world directly)
maybe it works in the simple_box example, but in the avian_3d_character example you first have to wait until the first replication interval hits, and then every 10 seconds the correction happens and the objects move significantly (indicating that the server's and client's simulation diverged)
you can see how at 0:21 all entities teleport to different locations
Iβve converted the spaceships example to be 3d instead and currently not experiencing any significant diverging between server and client
could you share the code?
Iβm in the process of converting * Iβm not done yet tho so itβs not published and available just yet
I tried the spaceships example and also could not get it to desync, even with a replication interval of 10s
How do you get it to desync?
Weird, maybe thats a system ordering issue?
With avian systems i mean
This is why desync detection would be nice. ggrs has a synctest mode where it rolls the simulation back a few ticks every time and checks if you still get the same result.
hm not sure i understand how that works
do resources map entity IDs?
Any tips on how to handle animation state in lightyear?
iirc u need to impl the mapper trait
clientside i guess
unless it interacts with the world then u would want it to run serverside

hmm, I don't think that's set up for resources; for components you need to explicitly call a function to get it to do that, and I don't see an obvious way to do that for resources
I think I'll just do a little sneaky and convert it to a single-component entity for now
hm okay
Hmm what if i need to know if playerr parrying or dodgin
And tell that to other clients?
I am thinking client side prediction with something spice
Client replicated Marker components
The avian 3d character example is not stable, there's extra rollbacks happening that I can't figure out, but it sounds like running it with a lower replication rate could help provide an intuition for why it's happening
They can if you implement MapEntities for them, same as components
oh, I think I see the mistake I made now, let me see if I can verify that...
yep, okay - I thought add_map_entities was on AppComponentExt, not on AppSerializeExt, so I was under the impression it was exclusive to components as register_resource does not return Self (i.e. the App)
it still doesn't work fully, but I suspect that's more an issue on my end and this resource doesn't need to be synchronised
I changed the bullet density ( in examples/src/protocol.rs:104) to 50.0, to be able to make the circles move more, and now I can get desyncs to happen.
if you skip to 0:40 in the video you will see the desync
I tried adding the remaining components that should potentially be rolled back as per avian documentation 1, but it still desyncs
For things like networking, it can be important to enable rollback for certain physics components and resources to avoid mispredictions. However, it can be unclear which components should be rolled...
I couldn't add the Collisions resource though because it doesn't implement Serialize/DeserializeOwned
@onyx anvil maybe what you just figured out could help solve the rollbacks?
Not sure, the github issue says Collisions is only needed "only if solver warm starting is enabled"
@pine cape See this boos it is a character customizer with server sided save files, and lobbies that display current players fighting
Very nice!
Btw can you please not call me 'boss'?
Sure thingy mr Peri
Do you have some pointers for how to do input replication with lightyear?
https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/protocol/component.rs#L633
Do I understand correctly, that if the server and client simulation are identical (deterministic & don't diverge in other ways) then the component shouldn't get updated here?
I added some logging here to try to find the cause of the desyncs, but what I'm seeing doesn't necessarily make sense to me
also it looks like the server may be overwriting the client's locally measured network characteristics?
I've added a event that gets written and sent in a system in shared.rs, and i have a system that triggers a run on on_event in both client.rs and server.rs. i only get the system running on client.rs and not server.rs. anything I should check? I tried turning off the system in client.rs to see if i get it on server.rs but I can only get it to run on client.rs. the event setup and handling is basically identical to the one i find in lightyear/examples/spaceships. and i tried running the example directly from the repo, and it seems to be working just fine there. not sure what im missing here.
app.add_event::<BulletHitEvent>();
app.add_systems(
FixedUpdate,
handle_hit_event
.run_if(on_event::<BulletHitEvent>())
.after(shared::process_collisions),
);
app.add_systems(
FixedUpdate,
handle_hit_event
.run_if(on_event::<BulletHitEvent>())
.after(process_collisions),
);
What do you mean by input replication? Where only the inputs are replicated and the server doesn't replicate the state?
Doing purely input-based replication (replicating only inputs and relying on the game being deterministic) which sounds like what you want, is not possible right now. I would check ggrs for that
If you're running Prediction and the server's update is the same as what the client had predicted, then correct the predicted component won't get updated
There's probably some client-specific filter (With<Predicted> or With<Interpolated>) that causes your event to only be emitted on the client
Will definitely look into that. Thanks
in this issue it sounds like it should already be mostly supported, but that it just needs an example https://github.com/cBournhonesque/lightyear/issues/185
Sure, feel free to open a PR if you have a better implementation!
I think it's more complicated than that. The tricky part is timeline and input-delay management.
This is my understanding:
-
current design: server replicates state to clients. Clients can do prediction. Clients run RTT/2 in the future compared to the server so that client inputs for tick
tarrive at the server on tickt. Input delay can be added to reduce the amount that needs to be predicted. -
deterministic lockstep with server-relay: all peers (client or server) can only progress if they have received all inputs from all clients. Clients need to run RTT/2 in the past compared to the server, and have RTT input-delay, so that it's guaranteed that all clients will have access to all peers. drawing
-
deterministic lockstep with server-relay and rollbacks: I think what Quantum does is all clients run
resim-capahead of the server. Every client that hasRTT/2 >= resim-capwill add input-delay to compensate. Each client has rollbacks betweenRTT/2andRTT/2 + resim-cap
drawing
So they each require different settings for the input-delay/timeline
It's not that much work, I guess it would just entail:
- creating maybe a ReplicationMode enum with 3 variants
ServerState, DeterministicLockstep, DeterministicWithRollback - adjusting the timeline sync and input-delay logic depending on the variant
- adjusting the rollback systems (for
DeterministicWithRollback, the rollbacks would be triggered based on inputs received, not based on server-state received)
but it's not entirely trivial
Are entity ids supposed to match on client and server or do we find their relation by looking at client id?
They don't match, but should be converted automatically by lightyear if you implement MapEntities for your component
Hmm yes I understand a few of those words Mr Peri
You can look at this example: https://github.com/cBournhonesque/lightyear/blob/main/examples/replication_groups/src/protocol.rs#L221
And you have to specify in the protocol that the component/resource needs to apply map_entities: https://github.com/cBournhonesque/lightyear/blob/main/examples/replication_groups/src/protocol.rs#L333
https://github.com/cBournhonesque/lightyear/pull/653 there you go π
What is "Quantum"? Is it another library?
It's a networking library for unity, but it's 1000$ a month I think
thank you. ive copied and implemented this on my server too the exact same way thatβs done here in the example. I now successfully get events to trigger handle_hit_event which I didnβt get previously but now what I get from connection manager via calling client_entity, itβs not matching the entity that is being queried in player_q in the system itself. When I put victim or shooter entity from connection managerβs client_entity into player_q.get_mut, I get no result. Not sure why. maybe what youβre pointing at is what I need to do to get these matching is to put on MapEntities on the player component for it to work. But from the comments it seems only necessary if I know my entity has components with entities inside it. (Children entities) .
Wow. π
A crime
1k? crazy
commercial gamedev is so insane sometimes
@pine cape saw this randomly btw
still crazy prices
I thought this would just give a warning, calling clone() on Copy types isn't allowed?
apparently not if you disallow that
why did you clone it anyways if its copy?
Because it does the same thing, I might have added Copy afterwards
i guess its bad practice because using copy ensures that the process uses memcpy while clone can arbitrarily construct new types
should prob also be minimally faster during compilation because the compiler knows it can just insert a memcpy instead of traversing the clone() call chain
oh actually two more things:
Copy-able data can easily be stored on the stack and doesnt need to impl Drop
Hi I have a noob question.
I don't understand how to spawn a player sprite for my entity, and have its transform follow the player's position.
I tried spawning the sprite like this
fn player_spawn(
connection: Res<ClientConnection>,
mut commands: Commands,
mut character_query: Query<
(Entity),
(Added<PlayerId>, With<Predicted>), // ??? This seems wrong
>,
asset_server: Res<AssetServer>,
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
) {
for (entity) in &mut character_query {
// ...
commands.entity(entity).insert((
SpriteBundle {
texture: texture,
transform: Transform::from_xyz(0., 0., 17.).with_scale(Vec3::splat(2.0)),
..default()
},
));
}
but I think my With<Predicted> is wrong firstly because it only spawns on one client, and then if I do Or<With<Predicted>, With<Confirmed>> it works but the sprite stays still.
For the object you control, you should spawn the sprite on the Predicted entity (similar to what you did), but for other players' objects, you should spawn the sprite on the Interpolated entity.
(spawning it on the Confirmed entity should work as well, are you sure that the server is correctly replicating your object movement?)
Thank you! This helped. I didn't understand what Interpolated was π
I converted a basic project to use lightyear. I got it to "work" after a while by looking at the examples but the replication is extremely slow. My fixed timestep is running at 64Hz, im using leafwing and avian3d. A simple debug log confirms that it is not the game state itself that is updating slowly. There is no link conditioner and the problem occurs with both lz4 compression and no compression.
Does anyone have any idea what could be going wrong here? I can send code of course, I dont know what to send so I dont want to spam this channel with that just yet
PS: the server is running as a standalone headless server and has the AssetPlugin, HierarchyPlugin, LogPlugin, MeshPlugin, MinimalPlugins, ScenePlugin, StatesPlugin, TransformPlugin bevy plugins
I would not use compression at all, i think it might be buggy.
You're only replicating 1 entity and having issues?
Did you try disabling prediction/interpolation and just checking what happens?
Also i think lightyear might have weird behaviours with 0 latency, so I would try adding a little bit of latency via the conditioner (maybe 5 or 10ms)
Let me try that!
hmm you're right, I set both the client and server to use LinkConditionerConfig::average_condition() and its already a lot smoother. still a bit choppy but already better
Questio is it possible to pick a server replicated resource, grab him in client mutate him and replicate that to server?
It's choppy because of the absence of interpolation/prediction probably; you're just getting replication updates every replication_interval
its not quite that kind of choppy, more like lag spikes every 2-4 seconds
like in the video but a lot less extreme
when i dont move for a bit and then move it will move smoothly for a bit and then once again have occasional mini freezes
Yes you can do 2-way resource replication, but you cannot rely too much on change-detection (i.e. the remote's replicated changes won't trigger change detection to not have an infinite replication loop).
that seems weird, do you have prediction on? Maybe you should log the amount of rollbacks you have.
That's not a lot of data so you should definitely be able to replicate that without freezes
I do have prediction on yes, ill log the amount of rollbacks
So ideally I should send an event which will alterate the resource in server?
No you can directly modify the resource directly on either the client or the server, and the changes will be replicated to the remote. It should just work out of the box; however to detect that the resource was changes I would use the MessageEvent<R> where R is your resource instead of using change detection directly
2024-10-02T15:49:31.089209Z INFO bevy diagnostic: packets sent per second : 124.548113 (avg 90.287223)
2024-10-02T15:49:31.089223Z INFO bevy diagnostic: KB received per second : 1368.916624 (avg 1300.508577)
2024-10-02T15:49:31.089226Z INFO bevy diagnostic: ping.rtt.ms : 100.993314ms (avg 94.946543ms)
2024-10-02T15:49:31.089228Z INFO bevy diagnostic: replication.prediction.rollbacks : 1382.000000rollbacks (avg 1038.083333rollbacks)
2024-10-02T15:49:31.089232Z INFO bevy diagnostic: ping.jitter.ms : 17.315650ms (avg 5.944809ms)
2024-10-02T15:49:31.089234Z INFO bevy diagnostic: packets received per second : 5465.650580 (avg 5192.487123)
2024-10-02T15:49:31.089236Z INFO bevy diagnostic: ping.pong_received_count : 214.000000 (avg 188.066667)
2024-10-02T15:49:31.089238Z INFO bevy diagnostic: replication.prediction.rollback_depth: 7.318379Average rollback depth (avg 7.298254Average rollback depth)
2024-10-02T15:49:31.089240Z INFO bevy diagnostic: ping.ping_sent_count : 217.000000 (avg 189.183333)
2024-10-02T15:49:31.089242Z INFO bevy diagnostic: replication.prediction.rollback_ticks: 10114.000000ticks resimulated during rollback (avg 7563.433333ticks resimulated during rollback)
2024-10-02T15:49:31.089245Z INFO bevy diagnostic: KB sent per second
hmm 1382 rollbacks
after running for maybe 20 seconds
probably something I did wrong then
Yeah that's pretty bad; I think the avian3d example also has a lot of extra rollbacks though, there is some unsolved issue related to avian3d.
It doesn't happen with the other examples
yeah I did notice it was slightly choppy on my machine
(but tbh even with the rollbacks your game should be able to run fine, I don't see why it couldn't handle rolling back for 7 ticks)
I have a question by the way: when I do
SharedConfig {
server_replication_send_interval: Duration::ZERO,
// the rest..
}
does that mean every frame as in every time Update is ran or every time FixedUpdate is ran?
referring to the /// A duration of 0 means that we send replication updates every frame
every Update
Im curious if limiting it will improve things
your framerate is probably 1000Hz or something
yeah my fans spin up every time I launch the server
I would set it to 10Hz to start, it wouldn't be noticeable because of prediction
thanks for all the help btw!
I have a world with hundreds of entities, each with large amounts of data attached to them (~64kB). I noticed that when I updated a few dozen of them at a time (i.e. a few megabytes of updates), I would miss a few updates on the client, so that the client world was left in an inconsistent state.
I assumed that there was a limit to how much Lightyear could send as part of its entity updates, so I switched over to manually sending a message for each entity update (including when a player connects), but now I'm seeing very few of them make it through to the client; of the hundreds sent on join, only a few make it, and very few messages after that on that channel are received (i.e. changing the chunks results in messages being sent on the server, but very few making it to the client)
my questions:
- are there limits on how much I can send/receive per tick in Lightyear? are they different between component updates and messages?
- should I be expecting an error when doing this? I noticed there was a PR to introduce errors for messages that are too large, but I'm not getting any (I'm
unwrapping all of the sends); is this because I'm sending many medium-sized messages, instead of one large-sized message? - how can I debug what's going on here?
- should I use some other communication mechanism for this?
for context:
- the entities are voxel chunks, with a component storing thousands of voxels in a chunk
- I'm fine with this being totally unusable over the internet right now, I'm just doing local testing to prove out the concept
-
There are not limits defined inside lightyear; if you're testing locally I would expect you to be able to handle the load. I would have to think about where the potential bottlenecks are (channel buffers, etc.)
-
I don't think there would be any errors when doing this; ComponentUpdates is an unreliable channel so it's expected that not all updates make it through. I don't really have any stats per channel available right now unfortunately. There is this StatsManager that tracks overall packet loss though.
-
I would try to add debug logs mainly around the Channel, MessageManager and Header code to try to understand if the packets get lost, or if there's a problem processing the messages inside the packet.
I had this PR to try to inspect the behavior of sending many large packets: https://github.com/cBournhonesque/lightyear/pull/569/files#diff-6b5453791c93658889b261b77e3b6f0aeef8d8d6f9b94703274c021ef5f1e058R35
(pressing M will send 2000 packets of size 16Mb)
Adding a bandwidth cap should ensure that the game is still playable even while big packets are consuming all the bandwidth, but a lot more testing is needed.
If you're using a reliable channel, I'm surprised that the message is not being received. There is a resend_delay where if we didn't receive an ack for that packet after e.g 1.5*RTT, then we try to send it again. Maybe this leads to the entire channel being saturated as lightyear keeps trying to resend the large messages? Maybe try increasing the resend_delay for your channel
I added a framerate limit to my server and its a lot better now! Even with the server replication send interval at 50ms it was still sending 7000 packets per second but now its more like 80 per second
fantastic, thank you! I'll follow these up - the large-packets PR looks a lot like what I'm doing (down to the names), so that should hopefully serve as a good template :)
That sounds like a bug; are you sure? framerate limiting shouldn't really affect anything if you have replication_send_interval set to 50ms. There's a bit of gotcha, but the sent_interval has to be set on both SharedConfig and on ServerConfig.replication, like so: https://github.com/cBournhonesque/lightyear/blob/cb%2Ffix-interpolation-auth/examples/common/src/app.rs#L194-L194
Did you do both?
Yea it's very misleading right now. The main on is ServerConfig.Replication.send_interval; I still need it on SharedConfig because the client uses it to estimate some interpolation values..
@pine cape I think I found the fix for my issue #655, I'll submit a PR if it works
nevermind that, it seems to be a bug in leafwing
I'm a silly goose, the issue was on my end - I forgot that I had other replicated components on the entities, so I had a conflict between the local chunk entities I was spawning and the remote chunk entities arriving
seems to be all working now, thanks for the help!
glad it was resolved!
replicating Transform rather than Position/Rotation for physics stuff might be a bit smarter, just because it doesn't create conflicts between other stuff you'd like to replicate as well
you can't really replicate both without some extra logic to prevent sync loops
Gonna try making an example for rapier too, you okay with that?
I found out that since their word is "separated", the system alignments became quite simple.
oh hmm, I think I've been being silly. I should be putting most components on the Predicted entity shouldnt I
the non-predicted entity should mainly just be a dummy for updating/seeing current server state
what would be the best way to add shared components that you'd want to be on both the Predicted/Interp player and the server entity?
I can think of maybe a filter for it like:
Query<Entity, (Or<(With<Predicted>, With<Interpolated>, With<HasAuthority>)>)>
But HasAuthority could be on the Confirmed entity as well right?
or maybe it should just be Without<Confirmed>? although I worry about that maybe not existing for a couple frames when the entity is spawned
this is specifically for components that I don't really want to replicate, they are just extraneous for stuff like rendering (and would be nice on the server as well since having a non-headless mode for the server is nice for debugging things)
Yes I think that's pretty good π
The client Confirmed wouldn't have HasAuthority if you don't have client to server replication
In general it's better to use With than Without I think, to handle some edge cases with host server mode
would this break with host server though?
Actually I guess it doesn't matter since you wouldn't have predicted ones since you are the server
No, on the client acting as server, you would only have 1 entity, which is both Confirmed and Predicted, or Confirmed and Interpolated
(I still add Predicted or Interpolated even if you only have 1 entity, so that your existing systems that rely on Prediction/Interpolation still work)
Hey, I'm seeing very strange behaviour with leafwing replicated inputs, it seems that an action's "just_pressed" is getting played back twice.
I have a "shoot" action on left click, which I read with the "actions.just_pressed" method, but with a single click the action is being played back on two different ticks. I have this code in a system that runs every FixedUpdate:
if actions.just_pressed(&PlayerActions::Fire) {
println!(
"Just pressed `Fire` on tick: {:?}/{:?}",
current_tick_or_rollback, current_tick
);
}
and I'm getting the following output (after only a single click):
[CLIENT 1] |Just pressed `Fire` on tick: Tick(1148)/Tick(1148)
[CLIENT 1] |Just pressed `Fire` on tick: Tick(1148)/Tick(1148)
[CLIENT 1] |Just pressed `Fire` on tick: Tick(1149)/Tick(1149)
[SERVER] |Just pressed `Fire` on tick: Tick(1148)/Tick(1148)
This happens when at times when there's many rollbacks going on (currently due to desynced collisions in Avian, but that's a different problem)
Any ideas on how to debug this?
Is your 'shoot' action also happening in FixedUpdate?
What are your InputConfig settings?
Is your 'shoot' action also happening in FixedUpdate?
I'm not sure what you mean, leafwing is doing all the input handling. Do I need to explicitly configure anything so that it works in FixedUpdate?
This is how I set it up:
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Copy, Debug, Reflect)]
pub enum PlayerActions {
Move,
Aim,
Fire,
}
impl Actionlike for PlayerActions {
fn input_control_kind(&self) -> InputControlKind {
match self {
PlayerActions::Move => InputControlKind::DualAxis,
PlayerActions::Aim => InputControlKind::DualAxis,
PlayerActions::Fire => InputControlKind::Button,
}
}
}
impl PlayerActions {
pub fn default_input_map() -> InputMap<Self> {
use PlayerActions::*;
let mut input_map = InputMap::default();
input_map.insert_dual_axis(Move, KeyboardVirtualDPad::WASD);
input_map.insert(Fire, MouseButton::Left);
input_map
}
}
As for the InputConfig, it's the default
InputConfig {
packet_redundancy: 10,
send_interval: Duration::default(),
}
@pine cape I created a minimal reproduction project based on the simple_setup example: https://github.com/mbrea-c/leafwing-lightyear-bug-repro
Oh
how do I add non-leafwing inputs?
since those still need to be replayed, can't be as simple as a send_message
Without giving it too much thought, I would probably just add another leafwing ActionState and set it manually
yeah I thought about that but it seems kinda silly given that leafwing is optional
just not seeing an example with non-leafwing inputs
the other problem is this input isnt really a like
Axis or something
I could use leafwing to transfer it I guess but thats also just jank as hell
Depends on the kind of input, but in my case I'm adding a "cursor position" input as an dual axis:
pub fn update_cursor_aim(
mut players: Query<(&Player, &mut ActionState<PlayerActions>), simulated_server_auth!()>,
q_window: Query<&Window>,
q_camera: Query<(&Camera, &GlobalTransform)>,
local_client_id: Res<LocalClientId>,
) {
// get the camera info and transform
// assuming there is exactly one main camera entity, so Query::single() is OK
let Ok((camera, camera_transform)) = q_camera.get_single() else {
warn!("Cannot get mouse pos, there isn't a single camera!");
return;
};
for (player, mut actions) in &mut players {
if player.0 != local_client_id.0 {
continue;
}
if let Ok(window) = q_window.get_single() {
if let Some(world_position) = window
.cursor_position()
.and_then(|cursor| camera.viewport_to_world(camera_transform, cursor))
.map(|ray| ray.origin.truncate())
{
// only update the cursor if it's changed
if actions.axis_pair(&PlayerActions::Aim) != world_position {
actions.set_axis_pair(&PlayerActions::Aim, world_position);
}
}
}
}
}
which runs here:
app.add_systems(
PreUpdate,
inputs::update_cursor_aim
.run_if(resource_exists::<LocalClientId>)
.before(InputManagerSystem::ManualControl),
);
Feels hacky but does the trick
ah InputPlugin::<Input> seems to be it
Lwim should probably be treated as a library that hasnβt been upstreamed yet, but will be. Itβs optional today
Yeah but lightyear will still need a way to send non-button/axis inputs
one off messages are good for just saying things like "player wants to buy an item" but for gameplay inputs it doesnt really work
lightyear does support one off messages but those are not inputs
Hmm, input plugin doesn't seem to be working here
not sure if I've missed something...
also wouldn't it be a good idea to have the leafwing input plugins be based on a run condition of is_server/client rather than on initialization?
e.g. I have a client that boots up a local server later on
input::native::InputPlugin is the non-leafwing version of inputs.
I think the book talks a bit about it, + some examples don't use leafwing (for example simple_box if I remember correctly)
Okay to whon should I attach my other player visuals? To predicted or to replicated? I am guessing me player I should attach to predicted
But other player replicated right>?
For yourself: predicted, for others: interpolated
thank you mr peri
hi, i was wondering if it's possible to replicate a component only to the client(s) that are controlling an entity, or do "controlled only" components need to be added to a child entity that is only replicated to the entities that control it?
I'm not sure what the second part of your sentence means, but you can set the ReplicationTarget to be the same as the ControlledBy target
sorry, i want some components to only be replicated to certain clients, for example the controlling client
for example say i have an entity with an item container component, but i don't want it to replicate to clients unless they own it (like a player's inventory) or they're specifically added to a list of clients to replicate to (a player opening a chest to see its contents)
the entity has other components on it such as position/etc that i still want replicated at all times so the players know it's there
hi, my I ask what is the best way to send player inputs from server to client?
context: I am having a server authoritative multiplayer game, I want clients to be able to simulate other clients based on player input, however, I still want the server to have the final say on the player's positions. Also, I want the player to be spawned from the server side as some stuff needs to be generated from the server side and then replicated to the clients
you could attach these to another related entity and limit replication of that entity to said client
other than that dont think what you are describing is possible at the moment
that's what i was gonna do if there weren't a better way. thanks π
wanted to know what exactly does this mean?
ERROR lightyear::client::input::leafwing: received input message for unrecognized entity entity=Entity { index: 22, generation: 1 } diffs=[[], [], []] end_tick=Tick(3264)
Thats weird, maybe the client wasnt aware that the player had despawned serverside and tried to continue sending inputs for it
don't think so, I did not despawn anything on the server, all I did was spawn an entity on the client with:
pub struct ReplicateInputBundle {
pub id: PlayerId,
pub replicate: client::Replicate,
pub input: InputManagerBundle<PlayerAction>,
pub prepredicted: PrePredicted,
}
then on the server, I just did this:
pub(crate) fn replicate_inputs(
mut connection: ResMut<ConnectionManager>,
mut input_events: EventReader<MessageEvent<InputMessage<PlayerAction>>>,
) {
for event in input_events.read() {
let inputs = event.message();
let client_id = event.context();
// Optional: do some validation on the inputs to check that there's no cheating
// rebroadcast the input to other clients
connection
.send_message_to_target::<InputChannel, _>(
inputs,
NetworkTarget::AllExceptSingle(*client_id),
)
.unwrap()
}
}
Hm no idea then :/
does channel direction when registering the components affects the initial replication from client to server?
You can easily control when a component is replicated, via it is network target. And by inserting the replicate component
You can use https://docs.rs/lightyear/latest/lightyear/shared/replication/components/struct.OverrideTargetComponent.html to override the replication target for a single component. So you don't need to create a fake child entity!
You can see it being used here for instance
There's probably better ways to design this; see https://github.com/cBournhonesque/lightyear/issues/540
There's nothing right now that would automatically sync that with the list of controlling clients, though
This component lets you override the replication target for a specific component
bevy_replicon has a disscution including a summary of planned implementation details here projectharmonia/bevy_replicon#304
You can check this example: https://github.com/cBournhonesque/lightyear/tree/main/examples/avian_physics
The inputs are broadcasted to all clients, so that all clients can run prediction on all other clients.
(specifically this system)
This example: https://github.com/cBournhonesque/lightyear/tree/main/examples/spaceships also does this
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
Things are a bit trickier with pre-prediction; you might not be handling it correctly. When the server receives the PrePredicted entity from the client; you need to add Replicate to it, like in this example: here
I would advise you to just spawn the players on the server at first
Hey thanks! I managed to get the preprediction working! Just curious, if Iβm just replicating the input only (the player itself is spawned by the server with prediction turned on), what should be the SyncTarget be? Should it replicate or interpolate at all?
Also in my working example, there are jittering happening on the physics side of things. Not sure whatβs wrong, could be because Iβm adding correction to linear and angular velocity?
thank you this is perfect
hmm interestng the system that inserts the interpolated component doesnt seen to run at the same time as the system that add_interpolation in protocols
To be expected but man did that screw up my logic
You should have SyncTarget.prediction = All, so that all clients have a Predicted entity for all other clients
As a first check try adding a lot of input delay first and see if the jitters go away.
Otherwise jitters are expected since you're correcting when you mispredicted other clients' inputs
I see. Also for getting our own input we can just do Query<ActionState, With<Predicted>> right? This will give us our own input as well as other playerβs. I am juz afraid that our own input is not in this query but rather the networked version of our input, if u get what I mean π€£
ok increasing the delay ticks to 10 reduces the jittery issue by alot
Well you shouldn't send your own input back from the server to the client, only other clients'
I see, I am using a room manager, but there is no "send all except" for send_message_to_room function, or do I have to manually identify the other clients in the room and send them?
Yep :p
Question let say I have an entity that is predicted, that should contain the following component according to it is protocol.
How can I guarantee my system logic only runs when predicted and player visuals are in my entity?
.add_prediction(ComponentSyncMode::Once)
.add_interpolation(ComponentSyncMode::Once);```
It seens the systems who do the sync are in different stages
Predicted inserted first then PlayerVisuals
@pine cape i'm working on deployment stuff with edgegap - current problem is (i think) that the public_ip of the gameserver isn't something you can bind to when the server listens (because AWS maps it using NAT), but it's ok because the server listens on all IPs. however, i don't easily have access to the private IP of the server to add to the internal_addresses bit of the connect token, when issuing the token. so i don't think my connect tokens are valid - server just complains it can't decode packets. Is there a way to tell the server what its public IP is (even if not technically listening on it), so my connect tokens are valid? i've a feeling that might be easier than exfiltrating the private IP of the server and storing it so the matchmaker can include it in connect token calculations.
here's my layout. i've nearly got it working end-to-end.. i think it's just my connect tokens that are generated wrong atm. will probably attempt to make a productionized version of the spaceships demo once i have it all sussed.
I guess you can have filters like (With<PlayerVisuals>, With<Predicted>) ?
Ah it is okayt found a solution
What I was trying to do, and I think it was mostly working is to have my webserver that talks to Edgegap in rust. That webserver calls the edgegap API to get the public IP of the server and uses that to generate a ConnectToken that it sends to the gameserver via https.
Am I understanding correctly?
that's basically what i'm doing, my matchmaker service generates the connectoken using the gameserver's public ip. not sure why the gameserver cant decode packets, my token must be wrong somehow
let token = ConnectToken::build(
server_addresses,
state.settings.protocol_id(),
client_id,
state.settings.private_key_bytes(),
)
.generate()
.expect("Failed to generate token");
let token_bytes = token.try_into_bytes().expect("Failed to serialize token");
let token_base64 = BASE64_STANDARD.encode(token_bytes);
where server_addresses is SocketAddr of the gameserver's public ip, which i tell the client to connect to
i was wondering if it's going to fail on server address checks, since the public ip of the server used in the token isn't known to the gameserver bevy app
probably
you could just use a uuid assigned to each server instead
@pine cape Mr Peri question would you care to review my current project code? Is basically a char customizer with server sided saving mechanics.
I found it it would be interesting to add a similar example in lightyear and also since I am newbee I am not sure what i did was right
(working now, looks like the IP part of the token isn't a problem. i think i had a stale container confusing matters.)
hmmm
Maybe in a couple months but I'm unfortunately too busy right now
Cool yeah, it worked for me with just the server public ip
Hm, I can see this error message on my deployed game server quite often:
[2m2024-10-09T09:47:11.024374Z[0m [33m WARN[0m [2mquinn_udp[0m[2m:[0m sendmsg error: Os { code: 90, kind: Uncategorized, message: "Message too long" }, Transmit: { destination: 176.36.215.158:52028, src_ip: Some(10.128.0.3), enc: None, len: 1452, segment_size: None }
And a lot of clients fail to establish a connection properly (because of it I believe).
Then clients try to reconnect, and the server spams with errors that a connection token is expired (does it get invalidated after the first connection attempt?)
Not quite sure why this is happening. Is it possible that my configuration is incorrect? Or is it an issue in lightyear or one of its dependencies?
Is it possible that my server assumes a higher MTU? I'm using GCP, where it's 1460 by default I believe. Though the error message mentions 1452. Maybe there's some sort of header which adds to the 1452 which then exceeds 1460
tokens are indeed only one time use
I found that lightyear has a hardcoded constant:
pub(crate) const MTU: usize = 1472
I'll try to change the MTU value in my GCP VPC network to 1500. But I guess it would be good for lightyear to have it configurable as well
Changing MTU made the error disappear, but that didn't help to fix clients being unable to connect :D
so I tested on Safari first - it failed
Google Chrome - connected
opened another tab in Google Chrome - failed again
I guess I need to get more debug logs to understand what's going on
from the client perspective it seems that the connection just times out.. and it only happens with my GCP deployment, not when I'm testing things locally. (But there's a lot that possibly differs: TLS, no latency - for starters)
ok, it might be just a browser thing. I can connect from my desktop client to the GCP server no problem
Tokens expire after some time; it's defined here: https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/client/config.rs#L28
Are you using UDP or webtransport? I don't think Safari supports webtransport yet: https://caniuse.com/webtransport
When you're connecting to the browser, did you first generate new Webtransport certificates?
hey @pine cape , would just like you to know that 0.15 leafwing breaks wasm but has now been fixed in its 0.15.1 version. however 0.15.1 is breaking lightyear. I know you said further up that you're busy lately so I dont wanna stress you on this. not sure if im good enough at this but ill see if I can make a PR for this issue.
im working on a PR rn. hopefully I do it correctly. forking lightyear, then making a new branch called "fix-for-leaf-0.15.1"
building it rn to check if compile errors are gone
I thought this fixed it: https://github.com/cBournhonesque/lightyear/pull/661
But maybe there's something more to change?
oh right. I think im wilding here.
oh I see. im on 0.17.0
the fix is found on main
ill try changing to main
main builds fine, remember to cargo update to pull in latest versions of LWIM if you have your own dep on 0.15
I updated my LWIM version to 0.15.1 in my cargo.toml. I'm getting other errors now tho not related to LWIM π₯² any good way to double check installed crates in my project dir?
i know of cargo install --list
but thats kinda global isnt it
you could always have a look in cargo.lock
oh right
if you're on lightyear main and lwim 0.15.1 you should be ok
looks good there
i tried cargo clean just now
building a new one to see if it clears
it now says source git+ instead of registry+. so it should be getting its code from main now
alright LWIM support looks good now and is cleared from compile error. but now I see something new.
this happens when i try to run "trunk serve" on lightyear/examples/spaceships
webtransport both on desktop and browser
I think it used to work on Safari when testing locally (but I could be wrong here), though it's not reliable on Google Chrome either
do you mean the server Identity? if so, yes. It's generated on each server boot (and then its digest is posted to VM metadata, which is read by a client via a Cloud Run Function, details are kinda irrelevant, but I'm just confirming that I do pass actual certificate digest, etc), so it's definitely not an issue with expired/mismatching certificates
it kinda seems that only first Google Chrome client can connect, and all the following attempts to connect from other tabs fail
it's definitely not an issue
I'd rather say it's "unlikely" since I can't be 100% sure ofc, but as I said, just after I failed from Safari, I was able to connect from Google Chrome, and then it again failed even on Google Chrome (all the attempts were made within a single 2-3m timeframe)
it seems like it might make sense to consolidate InputPlugin and LeafwingInputPlugin somewhat
leafwing's integration definitely seems much more robust so I'm assuming its just a lot of effort
Lmao, it was connection tokens all along. It turns out, browsers re-use the same TCP connection for different requests! And even for http1?.. O_o (Since my hyper server uses the http1 module)
And my server was increasing client_id counter not on each http request but on each connection... Which is why my different tabs were using the same connection token and their webtransport connection requests were ignored, lol
browsers re-use the same TCP connection for different requests
which I mistakenly thought they started doing only for http2
Bit confused by what's going on in the spaceships example here: https://github.com/cBournhonesque/lightyear/blob/a7aed471ee6e35e937cfa6ff3b6baec1d245fc05/examples/spaceships/src/client.rs#L173-L223
why not just apply the input buffer action if it exists?
and why does it use the active component instead of just the input buffer action
I feel like I'm missing something critical here
they way the action state system works is that for remote players, say you've received inputs for ticks 1,2,3 and now it's tick 4 but you didn't receive an input for the remote player for tick 4 yet, the remote player's action state is populated from the input buffer value from the last known input, ie tick 3.
so all that noise is basically to figure out how stale, if at all, the inputs are, so that the game logic can behave differently if it wants to depending on staleness.
for example, you might want to keep predicting forward motion, but only for 3 ticks of stale inputs, after that you might either decay the input force or set it to 0
the simple approach of just using the action state value would work fine, and be a lot less code, if you don't care about behaving differently depending on how stale the predicted inputs are
i can't remember if the spaceships example actually uses the staleness value in its simulation, but i wanted to figure out how to extract that info
(ah in fact you can see it only reuses the most recent input up to MAX_STALE_TICKS (6) ticks old)
a that's a fun one.. yeah http1.2 keepalive i guess
What I didn't expect that it re-uses connections even across different tabs :)
Though it totally makes sense from browser's perspective
Heyo, am I correct in assuming that ActionState<...> from a server-controlled entity is supposed to be replicated to the clients for prediction?
It seems that the confirmed entity from the server is getting the ActionState<...> (but with only empty maps?) but it isn't being mapped to the predicted entity below. Not sure if this is intentional, a bug or a skill issue, gunna take a break and mull it over
do you mean actionstate of other players? if so, yes it should be there. the simple_box demo should show that working i think
hmm, I booted up the avian3d example and it seems to have the same issue from the looks of it
ah simple_box doesn't use the leafwing plugin, i meant avian_physics
you should get ActionState component on the predicted entity for remote players, on your client
I'm using a host-server and it doesn't seem to be sending it's own inputs to the client for prediction
I assume the host is also supposed to send actionstate?
ah i'm unfamiliar with any quirks of host server, but pretty sure remote players' Predicted entities should have an updated ActionState, otherwise your client wouldn't be able to predict their movement (like how avian_physics works)
Yea, that's my assumption too. My code is basically a re-structured version of the avian3d example, maybe I have to send the host-server inputmessages seperately
perhaps someone else who's used host-server can comment. does it work if you run in dedicated server / client mode? assuming you didn't refactor all that away..
my game only runs dedicated server style so can't check
I assume it would since the server is receiving client inputs properly and runs smoothly
the avian_3d example w dedicated server seems to do it correctly
On the host-server, the Predicted and Confirmed entity are the same so the client/server can look at the ActionState and InputBuffer directly
You shouldn't need to do anything different
The screenshot and the behaviour is happening on the client, with a predicted entity that the host-server controls
If you boot up the avian_3d example and start one host-server instance and a client, the Confirmed player from the server has an actionstate, but the predicted which points to it does not (at least on my end)
And it doesnt seem to be recieving any input messages
(Assuming it's even supposed to?)
When reading InputMessages like in the avian_3d example are the events exclusively from remote clients or are events from a host-server local client also supposed to be included?
Can client send message to other clients?
I haven't written avian_3d so I'm a bit less familiar with it, I would look at avian_physics instead
Yes there is a send_message_to_target function
oh my so usefull i love it
In this case I should log the message as bidirectional in protocol right?
Yes
I did a little investigation on why my headless server app compiles bevy_render and found this: https://github.com/cBournhonesque/lightyear/issues/669
Gotcha this makes sense! Ty 
Is this normal?
getting jitter when this just starts to increase rapidly
probably just means my prediction set up is still incorrect since it isn't converging I'm guessing
It could be normal, if you have 2 players you could have many mispredictions because you don't know the other player's inputs
Question there seens to be more than one client id in lightyear? Which one should I be utilizing as a premise of my resource, there is one which is a u64 and another which is some sort of enum
the enum one says if the player connected from Netcode or Steam or plain UDP whatever transport you're using. each one has a clientid (u64) iirc. i just call .to_bits() to get the u64
you might need to pay more attention if you have players connecting from multiple different transports at once
Ah okay thanks
any advise to which schedule a camera follow system should be in with lightyear + leafwing input manager?
avian has this suggestion: https://docs.rs/avian2d/latest/avian2d/#why-does-my-camera-following-jitter
Avian Physics
if you're not using avian, i think just omit the .after(PhysicsSet::Sync) bit
im also adding ".run_if(not(is_in_rollback))"
could this have any effects i dont want?
PostUpdate after TransformPropagation should work, that's what I used for cycles.io I believe
it makes sense to NOT move the camera during rollback yeah
If using physics sets in rapier you need to set after syncpropagation honho
Ok so I finally got wasm compiling. But page is white
Something up with my target stuff probably
The cfg target family wasm thing in my client code is greyed out. Could be related.
Hi I'm getting an error while sending a message
Failed to send message: MessageProtocolError(MissingSerializationFns)
In my protocol I've already registered the failing message
app.register_message::<Message1>(ChannelDirection::Bidirectional);
And I'm not doing anything special sending the message
connection.send_message_to_target::<Channel1, Message1>(
&mut event.message,
NetworkTarget::Only(client_ids),
);
When I run with debug, I do see register message Message1 right after add_message with the serializefns
Hm I can't think of anything obvious. Maybe you're registering Message1 again as something else?
Perhaps lack of derive
Damn, such a simple thing I overlooked... lol
I was using client::connection::ConnectionManager instead of server::...
thats prob just rust analyzer not being configured properly
you need to have its target set to wasm too
Does anyone have an example of using bevy_streamworks with lightyear using steam transport? Not sure if both need to/can call run_callbacks
@dull lion ^
No, not possible. Run your own instance of steamworks
hm does the leafwing integration need both the action state and the input map on the serverside (and if yes does the serverside map need to be the same as the clientside map)? doesnt really make sense for me because users should be able to change their keybinds in the menu or smth, thats like very common practice for most games.
@pine cape do you know (if u have time)? :)
i suppose the main issue would be differentiating between different input kinds (ie dual axis, button and the like)
ig i should just try to reassign a binding on the client and see what happens
I don't think the server needs to have an InputMap at all, on the client
but doesnt the server need to know what type of input it is?
And yes you should be able to just update the InputMap on the client anytime
okay thats good
No the server just knows the actions (jump), not the input (W)
oh yea makes sense the data would be stored inside the action state
okay awesome, but maybe we should at least note that somewhere in the examples
cuz they all have shared input maps
Is there a way to run a system only after the interpolated entities have been fully synced, meaning they all have their given components?
Like a sync is complete state?
.after(InterpolationSet::Interpolate)
smth like that
thank you blobby, you shall go in my default pics for game folder now.
thats someones artwork of scp-999, prob copyrighted xd
no no, legal trouble does not matter into the folder with you.
Question lets say I have a simple playerposition component and a predicted entity with 4 sub child scenes, how can I guarantee those scenes follow along my player?
Scenes should be spawned as children of the predicted entity, and transform propagation will make sure that they follow the predicted entity
Question should I smooth out my predicted player transform according to time.delta_seconds()?
pub(crate) fn shared_movement_behaviour(mut position: Mut<PlayerPosition>, input: &Inputs) {
const MOVE_SPEED: f32 = 0.1;
match input {
Inputs::Direction(direction) => {
if direction.forward {
position.z += MOVE_SPEED;
}
if direction.down {
position.z -= MOVE_SPEED;
}
if direction.left {
position.x -= MOVE_SPEED;
}
if direction.right {
position.x += MOVE_SPEED;
}
}
_ => {}
}
}
/// Ensures transform of all player are adjusted
pub fn update_transform(
mut player: Query<(&mut Transform, &PlayerPosition), With<PlayerPosition>>,
) {
for (mut transform, player_position) in player.iter_mut() {
transform.translation += player_position.0;
// Add smoothing here? Like time delta and such
}
}
the answer was yes
err isnt ur position technically just velocity π
anyone know what i could be missing with net::ERR_CONNECTION_REFUSED ? ive made sure to give myself a new SSL certificate.
did you use the certificates/generate.sh command like in the examples? the rules for self signed webtransport certs are weird. no more than 14 days expiry, limited algos, etc
and you have to update the CERTIFICATE_DIGEST that the client declared, based on your generated cert
The custom certificate requirements are as follows: the certificate MUST be an X.509v3 certificate as defined in [RFC5280], the key used in the Subject Public Key field MUST be one of the allowed public key algorithms, the current time MUST be within the validity period of the certificate as defined in Section 4.1.2.5 of [RFC5280] and the total length of the validity period MUST NOT exceed two weeks. The user agent MAY impose additional implementation-defined requirements on the certificate.
I have some code that auto-generates the cert in rust when the server boots, and sends the digest to the client, which I plan to PR to the lightyear examples
you need to ensure the CN (or the SAN) of the cert includes the domain youre serving the index.html from too, ie 127.0.0.1
Correct I used exactly that one. Generated one, ran server, copy pasted the new one into settings.ron
Thanks for all that info, useful to know whatβs actually going on with certifications
the server outputs the cert digest, so make sure to compile that in as CERTIFICATE_DIGEST so the client knows it, it has to specify that to match the cert on connect
actually maybe that's not a const from lightyear itself... but you have to make sure the client knows the right digest when it configures the transport settings somewhere
since it has to provide it to the browser when making the webtransport connection
Ok so server gets it directly from the certifications folder files. Client gets it from assets/settings.ron, correct?
yeah that sounds correct
ensure the settings.ron digest matches what the server outputs
Iβve just been following the readme in spaceships
and use chrome, i don't think it works in ff
Yeah Iβll actually check that
Yeah I noticed Firefox had some uses with wgpu
openssl x509 -in certificates/cert.pem -noout -text to check the dates are valid and no more than 14 days expiry. should be fine if you used the script provided.
Iβm not currently on my computer but will check this tomorrow morning and make sure theyβre matching
Iβll also check so I donβt have the paths messed up.
Thanks for the pointers
Will probably get to the bottom of it with this
@pine cape should always_rollback work?
trying to turn it on to debug my issues with rollback not matching/not converging to something
I grepped it in the repo and it doesnt seem like its connected to anything?
I don't think the option does anything right now
You can turn on debug logs in prediction::rollback
To see which component is triggering the rollback
gotcha, do I need to patch the crate for that?
No you can just update your log filter in the debug plugin
o
Add something like lightyear::client::prediction::rollback=debug
thank you!
alright so i verified my ssl cert and its valid and within 14 days expiry
also verified that the cert matches in settings.ron and on the print in server
not sure whats going on. need to test more. same error still
Server on port 8080, thatβs correct right?
Clients come in via 5000
sounds right, I can check it myself when I'm at my desk in a couple of hours.
just tried the spaceships demo in wasm, working here. steps:
- run
sh certificates/generate.shfrom top directory cd examples/spaceshipscargo run -- server- copy the cert digest to the spaceships assets/settings.ron, looks something like "ee:44:35:01:ba:..etc"
- from spaceships dir, run
trunk serve - open
http://127.0.0.1:8080in chrome
@dreamy silo ^
Will try this again
Any tips when it comes to camera following predicted entity?
@unkempt sedge ^
Ah yes I had the same problem with rapier
And I was wondering if this was gonna be a pain again
i've been working on some tooling to make it easier to deploy lightyear games to Edgegap, which is a way to automatically spin up game servers in useful locations depending on where the players are. specifically interested in wasm atm for web games, so my system generates a self-signed cert when the server boots and transmits the cert_digest to the client along with the connect token, before it makes the webtransport connection. (connecting from native clients also works fine)
i took the lightyear spaceships demo and extended it slightly to use my bevygap_server_plugin for the server, and bevygap_client_plugin for the client, to make the connect token / auth / matchmaker stuff work.
i've containerized the server, along with an nginx based wasm container, which I deploy behind an existing traefik proxy i use for my blog. github actions does all the building and pushing to docker registries if you commit a v0.1.2 style tag.
i'm hosting a simplistic matchmaker which just lets edgegap decide what server would work best for you, along with autodeploying servers as needed. Takes ~3 seconds to matchmake if it has to boot a new server, and instant enough if there's a server running nearby already.
I still have plenty of tidying up to do, and probably some more error handling, but it's up here if anyone is interested: https://github.com/RJ/bevygap-spaceships (see the link to bevygap repo too).
If you click this with a chrome desktop browser, it's currently online so you can join a game. i'm connected to a server in london, not exactly sure if it will join you to the same as me if you've far away, or spin up another: https://game.metabrew.com/bevygap-spaceships/
This is really cool, well done
i've set the idle timeout pretty low in edgegap's server settings, so it should autoscale back down to zero deployments (ie, zero cost..) if there are no players connected. you do need to keep the matchmaker's httpd and webserver to serve the wasm+index.html online, but that is a low fixed cost with very minimal system requirements. those bits are all containerized too.
Will definitely use that for my game since the goal is to build a MMO game
Itβs a ambitious goal but at least Iβll definitely be learning alot of useful stuff during the process
This is amazing!! Will definitely take a look
im 99.99% sure ive followed these steps correctly. still connection refused.
thats my game tho. will actually go try the lightyear demo real quick
just to confirm theres something on my end
nope. same as on my game. this is the LY spaceships demo. also a new cert.
do I need to do any port forwarding on my router maybe
or at least unblock it
ok nah port forwarding is for something else
Does it work in native (not in wasm)? Maybe the connectToken is incorrect?
Yep works just fine in native
So strange
How do I check my connectToken?
maybe restart chrome? perhaps it's in a weird state
no luck on that part.
ill have to figure out how to dig into it. maybe try another system.
try in incognito mode? maybe a browser extension getting in the way
Good suggestion
does my demo work in chrome for you? https://game.metabrew.com/bevygap-spaceships/
Got a bit late here, I will try this demo tomorrow
Works for me 
this works
then i have no clue why my connection is getting refused
possible i just go straight to using bevygap
i'm not doing anything much different to the demos when it comes to connecting, except auto generating the certificate.
add some printouts of the cert digest in the client code, see what it's using
went back to this fact. how can I check this.
maybe when i run the script it makes for another domain than the one im expecting
the script:
# Generates a self-signed certificate valid for 14 days, to use for webtransport
# run this from the root folder
OUT=examples/certificates
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -keyout $OUT/key.pem -out $OUT/cert.pem -days 14 -nodes -subj "/CN=localhost"
uses CN (certificate name) = "localhost", so that's what you should use in your browser address bar
ive been visiting 127.0.0.1:8080
because thats what trunk says its serving at
game does load at that domain tho. just refusing any connections
i'm not sure if localhost:8080 would be required, i expect they are treated the same
I see the client tries to connect to https://127.0.0.1:5000/ according to the settings.ron
but server is being served at port 8080 with trunk. should that matter?
ive made sure the SSL cert is valid and correct on server and client. not sure what more I can try honestly lol
ill get to the bottom of it. will try bevygap tho. I could really use the smart fleet feature from edgegap
"When you see a message indicating a connection was refused (ERR_CONNECTION_REFUSED) this means that the server actively rejected the connection attempt. This is usually a firewall issue where the firewall rejects the packets."
so gotta be something about my setup
restarting my mac and updating the OS
Sorry for clogging lightyear thread with my isolated issue... Iβm definitely having localhost issues which is not related to this anymore. Iβll stop reporting here on my issues and instead find other places to get help if I need it
who am i gaming rn on bevygap spaceships lol
me
i saw the matchmaker logs scroll past so i opened it up
where in the world are you btw?
been working a few hours tryna figure out my localhost wasm issues. rare im stuck for this long.
might try to bake live servers into my game using bevygap
have you got any firewall or proxies or antivirus that might be getting in the way
this is what i can find on my proxies settings
not sure if any of these should be on
thats pretty cool
yeah that all seems fine, i dont think its that
will try a local setup now
ive also gotten my macos over to macos 15.0.1. wiped my dns cache. etcetc
(i mean locally without bevygap, there's a lot of containers to wrangle and general hassle atm, since i'm still making lots of changes)
ah makes sense
possible im just gonna continue my game development in native. and solve this issue later
or that it by a miracle goes away
game dev in native is faster and easier anyway, i only test wasm occasionally
exactly
benefits with wasm is that it requires minimal setup for users wanna join and play. letting others join in native requires some work from here.
since now im just joining locally via cargo run client -clientid
If my player should go where his camera is pointing at, how can I also update server as required? Should I just message it the direction of it is camera?
you can have the cam dir as input
honestly Confirmed entities might be better "disabled" in the future once we that ability
most of the bugs I run into are me modifying the confirmed entities by accident, but that's never been what I've wanted. just forgot that Or<(With<Predicted>, With<Interpolated>, With<HasAuthority>)> filter on some queries
man, still just having a lot of issues with mispredictions because of my KCC
my only guess right now is some sort of system ordering ambiguity because there is seemingly no connection to anything as to when it starts bugging the hell out
So I'm trying to get a very minimal example code running for a HostServer configuration and it seems like I'm missing something. When I try to connect the client in the same instance of the app the server is already running in, I get the following message in the console but it never actually connects to the server: client connecting to server 127.0.0.1:5000 [1/1]
When I try to connect from another instance of the app the client actually does connect.
Here is the code I'm running:
fn main() {
let shared = SharedConfig {
server_replication_send_interval: Duration::new(0, 0),
tick: TickConfig::new(Duration::from_secs_f64(1. / 64.)),
mode: Mode::HostServer,
};
let mut app = App::new();
app.add_plugins((
DefaultPlugins,
EguiPlugin,
WorldInspectorPlugin::new(),
ServerPlugins {
config: ServerConfig {
shared,
net: vec![server::NetConfig::Netcode {
config: server::NetcodeConfig::default(),
io: server::IoConfig {
transport: ServerTransport::UdpSocket(SocketAddr::new(
IpAddr::V4(Ipv4Addr::LOCALHOST),
5000,
)),
..default()
},
}],
..default()
},
},
ClientPlugins {
config: ClientConfig {
shared,
net: client::NetConfig::Netcode {
auth: Authentication::Manual {
server_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5000),
client_id: 1,
private_key: Key::default(),
protocol_id: 0,
},
io: client::IoConfig {
transport: ClientTransport::UdpSocket(SocketAddr::new(
IpAddr::V4(Ipv4Addr::LOCALHOST),
4000,
)),
..default()
},
config: client::NetcodeConfig::default(),
},
..default()
},
},
));
app.add_systems(Update, menu);
app.run();
}
/// Test interface for starting server/connecting to server.
fn menu(mut commands: Commands<'_, '_>, mut contexts: EguiContexts<'_, '_>) {
egui::Window::new("Hello").show(contexts.ctx_mut(), |ui| {
if ui.button("Start Server").clicked() {
commands.start_server();
}
if ui.button("Connect Client").clicked() {
commands.connect_client();
}
});
}
added transfer-encoding chunked streaming support to bevy_http_client, and now my begygap matchmaker streams progress back to the client while looking for a session
Could u explain that to me like I am 5 yo?
most http get requests just send you back a big lump of text in one go. with transfer-encoding chunked, the server can hold the connection open and send back small messages periodically. so i send a message every second or so with updates, until complete (then the connection closes)
similar vibes to a unidirectional websocket connection i suppose
it's how we did realtime web stuff before websockets were invented.. π΄
Now I understand
my run_rollback & update_action_state systems are ambiguous with eachother which sounds a bit bad
More similar to SSE, not websockets, no?
yep
idk who had the idea with the auto generating protocol id but i guess it should be possible to generate it based on the commit hash if you are building your client and server from the same codebase https://crates.io/crates/shadow-rs
i recently started using vergen to add build info to server startup logs, quite handy
shadow looks simpler to use actually, i may switch
twas me, ya that's probably the next best thing
maybe better, but I was thinking changes that don't change the underlying protocol might be good to allow through
I have no idea why it was even triggering they arent even in the same schedule lol
you could also just use the cargo.toml project version then :v
i think that might be too permissive and has the same problems as the protocol id as it is exists now, have to remember to update it manually
every major update = change in protocol id or smth
u should be doing that anyways lmao
no updating ur package versioning
unless you have like no users or smth
idk
then prob doesnt matter too much
I mean as an end user project project version doesnt matter
shrug thats like the location where i keep track of what version im at, which makes sense to let users know
commit hash is probably a better thing to use for that anyways because you can't mess it up, if its that hash you know the game version based on the git history
but you brought up a good point of using commit hash which I'll probably start using haha
made a jank component to force rollbacks every received server message
not sure what's up with it but I get really bad jitters from the rollback
almost looks like its overshooting it or running too far into the future? first part is with rollbacks, second without
or maybe something about input timing to the server...? I'm not sure
or maybe interpolation is being wonky, I'll try that first
I guess I'll also simplify my controller heavily for now to discount that as an issue
@inner hill i've noticed some jank similar to what you see sometimes, not sure what is causing it either. happens when latency is low enough that there shouldn't be any rollbacks. haven't got to the bottom of it, moved on to working on some other deployment stuff for now
aah yes
just as i am making a physical character controller
i see three people having difficulties
lovely
LET MAKE IT 4
Perhaps it is something correlated to the replication rate
@inner hill do you have a sample repo to reproduce this?
ill try to make one, was planning on splitting it down to just check if I messed up somewhere
Let's make it 5 lol, I'm running into the same issue
While trying to debug this I noticed that some inputs are getting played back on the wrong ticks in the clients (minimal reproduction repo: https://github.com/mbrea-c/leafwing-lightyear-bug-repro). Maybe not the root cause but it could be related
I didn't have time to debug further yet though
what ports did you setup in edgegap for the server?
just one, the port that ly listens on. 6000 something in my example, 6420 maybe. on phone atm
(UDP)
tried accessing my game after deloyment, but just says unable to connect. probably because i skipped setting up a port
this is the host address
it will map your port to a different one, so you need to use the API or look at logs to find the external IP and port
would a nslookup be sufficient
oh I found it
found the logs. something is not working.
[2m2024-10-20T12:55:09.928235Z[0m [33m WARN[0m [2mserver::server_plugin[0m[2m:[0m cert_digest: a0:a6:79:25:3e:d4:5a:d5:e5:d2:e8:72:05:3e:fb:0a:27:a3:49:ca:7f:49:4a:05:54:fd:2b:c0:e1:20:3d:b9
[2m2024-10-20T12:55:09.928259Z[0m [32m INFO[0m [2mbevygap_server_plugin::plugin[0m[2m:[0m BevygapServerPlugin::build
[2m2024-10-20T12:55:09.928262Z[0m [32m INFO[0m [2mbevygap_server_plugin::plugin[0m[2m:[0m Reading Arbitrium ENVs
[2m2024-10-20T12:55:09.929491Z[0m [32m INFO[0m [2mbevygap_server_plugin::plugin[0m[2m:[0m Setting up NATS
[2m2024-10-20T12:55:09.929635Z[0m [32m INFO[0m [2mbevygap_shared[0m[2m:[0m Setting up NATS, client name: bevygap_server_plugin
[2m2024-10-20T12:55:09.929656Z[0m [32m INFO[0m [2mbevygap_shared[0m[2m:[0m NATS_HOST: localhost:4222
[2m2024-10-20T12:55:09.932738Z[0m [31mERROR[0m [2mbevygap_server_plugin::plugin[0m[2m:[0m Failed to setup NATS: IO error: Connection refused (os error 111)
thread 'tokio-runtime-worker' panicked at /usr/local/cargo/git/checkouts/bevygap-2cc19569b69827ea/1ebce12/bevygap_server_plugin/src/plugin.rs:164:17:
Failed to setup NATS
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ````
oh right i see it in your readme now
Donβt wanna bother you too much. I tried your bevygap spaceships. Not sure where I put in my own nat configuration. Since currently itβs looking at 0.0.0.0:4222
it's environment variables, NATS_USER etc
can set in edgegap app deployment settings
I will spend some time documenting bevygap stuff better, but I'm on hol for the next week π
Learning as we go. I could potentially just install and run a nats server by putting it in as a job in the yaml file right?
Iβll let you enjoy your holiday πΈ
How is the pricing for that btw
I am half way thru writing the docker compose that runs everything for you. currently entangled with my other sites..
you only pay for hours that the servers run, Like EC2. I've set the application to kill deployments with no players after 10mins
so it should scale down to zero cost unless there are players
I plan to write a full tutorial to getting it running. happy to answer qs in the meantime but I might be slow to reply this week
Ah alright
Question when applying lets say a playerposition component. To my transform, should I generally interpolate it? Even tho in he is already being interpolated in sync
Is worth noting the visual entities are child entities, of interpolated player
Right now he just teleports e.e
My 2c: lightyear will never dictate any of this to you, or even guide you on it
Beyond examples
It will err on the side of efficiency and so yes you see the more efficient snapping behaviour prevails
Also think you may have a small bug where you're adding meshes to the wrong entity but that might be another issue π
shouldnt interpolated be the carrier of side player visuals?
No it must be Mr Peri would never lie to me
Anyways do sub-component inherit interpolation and such? For example my velocity, has two sub component linvel and angvel, they also would be interpolated correct? Does nested interpolation exist?
answer was yes yay
Almost got a nats server with TLS certs setup to run alongside bevy on edgegap. Self signed. But not sure what CN I need to assign it for it to make a valid cert. hostname?
@wintry dome ^
Generic like *.pr.edgegap.net
That still didnβt work for me, think I need public ip
my nats server is borrowing the letsencrypt cert for my domain, so i connect to it at nats.example.com and it uses a proper cert for nats.example.com that also provides https properly
you need the nats server somewhere public that edgegap deployments can connect to. test your connection from your machine with nats-cli first
you may have to change the nats connection code in bevygap if you use a self signed cert i'm not sure
So edgegap canβt use a natserver from within the same container?
generally the CN of the cert is just the domain name that you type in the browser, or the NATS_HOST
i wouldn't run nats on edgegap, it should be somewhere else i think. edgegap just for game servers
Gotcha
https://www.synadia.com/ have a free tier of hosted nats that should give you proper tls. i've not tried it tho
Im gonna do the thing and make myself a public natserver
Then ask that from edgegap deployment
if you have a vps or dedicated server somewhere you can pretty much just run nats docker and expose it, if you get the cert stuff working
Do I need a .env file with your bevygap example?
my env file contains NATS_USER, PASS, HOST and the EDGEGAP_API_KEY (or whatever it's called)
gotcha