#lightyear
1 messages · Page 3 of 1
The current approach is limited to 1 enum for messages, and 1 enum for components.
Prod
- easier to work with (lightyear internals can be typed), faster iteration
Cons - the user has to have a single enum; which means they can't split the protocol into different plugins
- it requires a lot of derive macro magic
it requires a lot of derive macro magic
As a simpleton, I'd be in favor of less magic 😄
I feel like the type registry pattern is more idiomatic bevy too
Also we haven’t seen it yet, but I imagine with a large game with let’s say, 100 different components, even if this ultimately is all one enum, that is a lot more difficult to parse through and maintain. Compared to e.g. some plugin that is responsible for NPCs that directly registers its component types. Also the compiler magic with the derive macro seems fragile, like the errors can be cryptic to figure out (such as a lerp referencing something you forgot to use)
@pine cape Do you know why it's not possible to access ping_manager from the ClientConnectionManager resource? The fields are pub(crate)'d on the ClientManager struct so the error doesn't make a whole lot of sense lol. Here is the test code:
fn xyz(connection_manager: Res<ClientConnectionManager>) {
let ping_manager = connection_manager.ping_manager;
// ping_manager.rtt()
}
It has to be pub for you to access it, no?
Oh, that makes sense. Is there any other way to access that? I was trying to add RTT and Jitter to the new "iyes_perf_ui" overlay
just exposed them: https://github.com/cBournhonesque/lightyear/pull/275
Really cool! I’ve been busy with life things the past few weeks but will be getting active again next week some time
I have a big PR that changes how the protocol is defined; instead of registering the protocol at compile time with two Enums, you can register them while setting up your app: https://github.com/cBournhonesque/lightyear/pull/278
We removed the derive macros and the protocol enums; instead we can register an enum piece-by-piece without an API that looks like:
app.add_message::<Message1>(ChannelDirection::Bidirection...
Does anyone have feedback on the new protocol API? https://github.com/cBournhonesque/lightyear/blob/cb/message-kind/examples/simple_box/src/protocol.rs#L113
Finally 💯
Looks very well so far.
This is looking great. Is corrector still needed?
Maybe not, since in most cases the corrector function is the same as the interpolation function. I'll just keep it for now just in case users want interpolation and correction to be different
Overall I like the direction- I wonder if it makes sense to group some of the registrations using the builder pattern? For example,
.add_component()
.with_interpolation
.with_interpolation_fn()
Etc.
Either way I think what you have is a big improvement
yes I was thinking of doing this as well; let me try to add it
Progress update on rust in rust. Got all of the physics update issues smoothed out. The blue cylinder represents an npc, entirely server controlled physics
Lets gooo :)))
🎉🎉🎉
It looks so gooooood
omg
I’d be curious to hear more about what you had to smooth out and how? Movement and collision look spot on.
How feasible is it to write a stress test that can test lightyear (and others) performance? Something that measures packet throughput, latency, fps, etc over a range or increasing entity count would be really cool to see.
in the examples, particularly the leafwing input or simplebox, the player is spawned by the client pre-predicted, but instead I have the server spawn the player to the client on connection (e.g. add a marker component like LocalPlayer with a replicate.add_target of just Single(client_id) on the player itself
I was fighting physics for a long time with the assumption of having the camera follow the player's Predicted entity which works fine 95% of the time, but when you try doing that over the internet there is enough jitter or whatever to make it stutter randomly. The breakthrough (before almost quitting several times LOL) was to interpolate the player back to themselves (like basically interpolation_target All instead of AllExceptSingle) and have the camera translate follow their interpolated entity. You technically live on a slightly different timeline than predicted or confirmed but this is actually closer to what rust does. And also this way you don't even need to bother simulating other players (or your own) physics client side because you can just rely on the interpolation and have the server smooth you out when that packet comes through
before almost quitting several times
lol yea I hear that - I’m going to have to simplify my approach a bit too. I can’t seem to get the prepredicted approach to work smoothly for the life of me.
Thanks for the insight!
Fwiw you can interpolate the camera itself too, to smooth out its following of the player even more
also another thing to save you 20 hours
capture the camera rotation from the mouse motion in PreUpdate with in_set(InputManagerSystem::ManualControl)
and don't camera.translate until FixedUpdate, (if using xpbd) preferably after(PhysicsSet::Sync)
Btw, PrePrediction should have no impact on smoothness; the only thing it does is avoid the small latency of waiting for a roundtrip to the server by spawning the entity directly on the client instead
Yea I understand the concept but in practice I just can’t get it to work smoothly. Something causes the client to lose its shit and drop frames and rollback nonstop
I went back to my renet branch and things work smoothly even with 2000+ entities and my own interest management. So I think I’ll have to dumb down my lightyear branch and go from there.
My avoidance of pre predicted player is more to do with server initiated control, like not spawning them in until you are ready to. I think pre prediction would be great for something like projectiles but clients spawning their own player sounds weird to me
Yea agreed. I’m my case the player may influence their player model but ultimately the server should control when, where and with what items they spawn with.
Hence my question about a stress test and benchmarks.
I think Periwink said in a github issue he's primarily focused on API usability and working out bugs and then performance improvements will come later. That said, it's already pretty decent, but I haven't pushed the entity count personally yet
When I started messing around with my project I went through every single networking library that's out there, and lightyear is by far the most advanced and convenient...... like no one else has even touched replication or interest management. And the fact that the transport layer is modular alone is unique. I cannot imagine using something like quinnet and having to waste time figuring out all of that stuff which detracts from the game itself
So I'm actually looking forward to seeing where lightyear goes, we are all early adopters lol
Totally agree, lightyear is definitely next level as far as features.
That said, I have a feeling some performance issues are lurking under the hood once it gets pushed harder than a handful of entities. I’m just not smart enough or proficient in Rust yet to prove it.
I feel the same way. I want to contribute to lightyear but I dunno if I'm smart enough lol. I can usually find where the problem is but my rust skills just aren't there.
That's very interesting; you should have mentioned those issues you ran into, because I ran into exactly the same thing!
There is actually a solution for this integrated in lightyear: VisualInterpolationPlugin https://github.com/cBournhonesque/lightyear/blob/main/examples/replication_groups/src/client.rs#L57
If you have a component that gets updated in FixedUpdate (which is usually the case for the Predicted entity), this will visually interpolate the component value in the PostUpdate schedule.
You can enable it for a given entity like this: https://github.com/cBournhonesque/lightyear/blob/main/examples/replication_groups/src/client.rs#L142-L142
And then you can make your camera follow your Predicted entity and it will appear smooth
Some more detail here: https://cbournhonesque.github.io/lightyear/book/concepts/advanced_replication/visual_interpolation.html
But your solution of using interpolation even on the client owning entity is interesting; I guess the main drawback is that it adds some input delay
There's definitely a few performance issues:
- I spawn a lot of systems (usually one or more per component); this can have an adverse effect on the scheduler (although I think bevy is trying to optimize the scheduler to be able to handle more systems)
- I think parallelization could be increased in a lot of places: optimize access by using fewer ResMut or splitting up resources, using
par_iterinstead ofiter - I want to avoid having any allocation on the hot path (receiving and deserializing packets). Ideally I would just be able to deserialize the packets into pre-allocated buffers with zero-copy, but it is far from the case right now. I think in some cases the deserializing does multiple copies, even...
Thanks, I'll check this out. I remember coming across this in the book but this now makes more sense in context. I think I accidentally reinvented this plugin with an added delay
Yeah, the problem is mostly that FixedUpdate doesn't run once per frame; so if you tie the camera updates to something in FixedUpdate there will be micro-stutters all the time. It was also driving me crazy, it took me a while to figure out.
But yeah the conclusion is that every visual component should be interpolated in some way or another, so that it gets updated once per frame instead of in FixedUpdate
I'm trying to upgrade to the latest commit and I'm getting this:
the package `lightyear` depends on `lightyear_macros`, with features: `leafwing` but `lightyear_macros` does not have these features.
I have these features on 'lightyear':
features = [
"steam",
"webtransport",
"render",
"leafwing",
]
ah damn, did i upload the wrong version
This is with the repo, I like to live on the edge lol
normally it should be fine: https://github.com/cBournhonesque/lightyear/commit/011c619e98e3fbca48cc595c82b7d72f17b65c91
oh weird, I think for some reason it was stuck on the 3990776 but i cleared out my lock file, looks like it's working now
On something like this, from the leafwing inputs example:
app.register_component::<PlayerId>(ChannelDirection::Bidirectional);
app.add_prediction::<PlayerId>(ComponentSyncMode::Once);
app.add_interpolation::<PlayerId>(ComponentSyncMode::Once);
Is ok to leave off add_prediction or add_interpolation on components that don't need it? Like you wouldn't lerp a ClientId. And this is separate from replication targets right? Like, by not doing add_interpolation on PlayerId I would still get PlayerId on the Interpolated entity?
You need add_prediction to specify that a component will be synced to the Predicted entity
But you can avoid specifying an interpolation function (no app.add_interpolation_fn())
You need add_prediction to specify that a component will be synced to the Predicted entity
does this apply to.add_interpolation()too? In other words, do you need.add_interpolation()to sync a component to theInterpolatedentity?
yep, same thing!
cool thanks
seems there are some compilation errors in the steam feature on main
```error[E0433]: failed to resolve: could not find wordbuffer in serialize
--> C:\Users\sqwee.cargo\git\checkouts\lightyear-2cfb5e6660946fe3\16b1c72\lightyear\src\connection\steam\server.rs:8:23
|
8 | use crate::serialize::wordbuffer::reader::BufferPool;
| ^^^^^^^^^^ could not find wordbuffer in serialize
error[E0432]: unresolved imports crate::_internal::ReadBuffer, crate::_internal::ReadWordBuffer
--> C:\Users\sqwee.cargo\git\checkouts\lightyear-2cfb5e6660946fe3\16b1c72\lightyear\src\connection\steam\server.rs:1:24
|
1 | use crate::_internal::{ReadBuffer, ReadWordBuffer};
| ^^^^^^^^^^ ^^^^^^^^^^^^^^ no ReadWordBuffer in _internal
| |
| no ReadBuffer in `_internal````
Thanks! should be fixed now
Btw you can run cargo bench to generate some benchmark results. The benchmark is very simple now (just replicate 1 Component to 1 or multiple clients), but hopefully more can be added in the future
I gave this a shot but I am still getting the jitter. That might be a system order issue with xpbd for me though. Requires more testing.
Also do you know if any change would have caused the order of entity spawning to change? Before Predicted entities always spawned before Interpolated entities. Now it seems one can happen before the other.
Or it might be something else weird. Like I feel like sometimes I'm not having Added<Predicted> happen
e.g. ```
Query<Entity, (With<SomeMarkerComponent>, Added<Predicted>)>
Is your code public?
There shouldn't be any change in the order of entity spawning; but the PR was very big, it's possible that some bug got introduced
Sorry for the randomization - I'm getting a panic due to an event not be registered by the time my server starts, did I miss something?
If you use pre-prediction, you need to make sure that direction is Bidirectional; also the protocol registration has to happen after adding the ClientPlugin and ServerPlugin
I've also seen cases where an entity doesn't get spawned; i'll have to investigate
you need to make sure that direction is Bidirectional
that was it, makes sense now in hindsight
fairly clean and painless upgrade overall, nice work!
It's weird, it is spawning the entity but not all of the components.
protocol for ref:
app.register_component::<NetworkId>(ChannelDirection::Bidirectional);
app.add_prediction::<NetworkId>(ComponentSyncMode::Once);
app.add_interpolation::<NetworkId>(ComponentSyncMode::Once);
app.register_component::<LocalPlayer>(ChannelDirection::Bidirectional);
app.add_prediction::<LocalPlayer>(ComponentSyncMode::Once);
app.add_interpolation::<LocalPlayer>(ComponentSyncMode::Once);
app.register_component::<Position>(ChannelDirection::Bidirectional);
app.add_prediction::<Position>(ComponentSyncMode::Full);
app.add_interpolation::<Position>(ComponentSyncMode::Full);
app.add_interpolation_fn::<Position>(PositionLinearInterpolation::lerp);
app.register_component::<Rotation>(ChannelDirection::Bidirectional);
app.add_prediction::<Rotation>(ComponentSyncMode::Full);
app.add_interpolation::<Rotation>(ComponentSyncMode::Full);
app.add_interpolation_fn::<Rotation>(RotationLinearInterpolation::lerp);
app.register_component::<LinearVelocity>(ChannelDirection::Bidirectional);
app.add_prediction::<LinearVelocity>(ComponentSyncMode::Full);
app.add_interpolation::<LinearVelocity>(ComponentSyncMode::Full);
app.add_interpolation_fn::<LinearVelocity>(LinearVelocityLinearInterpolation::lerp);
app.register_component::<AngularVelocity>(ChannelDirection::Bidirectional);
app.add_prediction::<AngularVelocity>(ComponentSyncMode::Full);
app.add_interpolation::<AngularVelocity>(ComponentSyncMode::Full);
app.add_interpolation_fn::<AngularVelocity>(AngularVelocityLinearInterpolation::lerp);
this should have all of them but only have the two velocities. and the interpolated one came through fine
only some of the time
so it's probably not the order like i thought, i just wasn't getting the marker component added
just had a run that spawned the entity with "Predicted" and nothing else lol
could you please file a quick issue?
there must be some ordering constraint missing somewhere
Did you notice this only on prediction?
https://github.com/cBournhonesque/lightyear/issues/290
I'm thinking it's just on Prediction because the other ones always seem to have the data. I am going to keep testing to verify that
seems only to effect predicted
yes, this makes sense
I added Predicted based on
// get the list of entities who get ShouldBePredicted replicated from server
mut should_be_predicted_added: EventReader<ComponentInsertEvent<ShouldBePredicted>>
I wonder if I should just base it of Added<ShouldBePredicted>
In general, I wonder if those replication-related events are useful
I've seen those ShouldBe____ components in the inspector but never really understood their purpose. Is it an implementation detail?
it's an implementation detail yes, it's how I notify the remote that they should create an Interpolated or Predicted entity
ah ok
One observation is, LinearVelocity is a component in the protocol but is not being included in the spawn. Is it related to predicting components that haven't been set?
(edit: nvm i tried with adding everything in the protocol, can still have an empty entity with only Predicted)
I'm not sure I understand the question
I released 0.14.1 with the fix
With the new runtime Configuration, you mentioned a dedicated Server that lists all lobbies, right?
Is that required? Can i manage the Server?
For example, steam offers its own lobby listing with matchmaking and everything. Could i somewhat easily integrate that?
Just currently thinking, not able to actually work on it currently. But very interested to Experiment with this later, as lightyear Looks more and more promising for my Projects
Ah i think you misunderstood; I just created a new example called 'lobby' (https://github.com/cBournhonesque/lightyear/tree/main/examples/lobby) where you can run a server which replicates a list of lobbies. The list of lobbies is replicated to clients, which can join them.
So there is no lobby listing or matchmaking in lightyear, it's just part of that one example
You might need https://github.com/Noxime/steamworks-rs to integrate with steam lobby/matchmaking
Ah yes then i misunderstood, very cool!
is there a reason that RoomId uses wrapped_id and thus has u16? I didn't see it used in the protocol, it looks like it's only used for the room management. I was thinking, that if this could be wider like a u32 then it is more convenient to generate RoomIds without having to worry about conflicting IDs or some auto incremented ID wrapping. There are some clever use cases of room, that are not "client_id first" like say that some loot box has items, and you only want to replicate it when the player is close enough or has accessed it, you could put the loot box entity in its own room and then join the client into it, and kick them out when they close the interface or move too far away
yeah i noticed that too recently and was confused by it; I think setting to u64 sounds good, with maybe helper functions to convert to/from entity
@bronze elm updated: https://github.com/cBournhonesque/lightyear/pull/294
Killer, thanks 👍
very cool how did you make the server list visible on screen?
I have two states, GameState and MenuState. OnEnter GameState::Menu I spawn a camera2d, and a root NodeBundle that has left and right pane nodebundle children, in a percentage split. The left pane I spawn the buttons and dont really touch it. The buttons trigger a MenuState change. And then you have OnEnter for each menu state that spawns your menu contents as a child of right pane, and i add OnExit that despawns everything inside right pane. Bevy UI doesnt have tables so the server browser is just a bunch of percentage width node bundles with text inside each
Cool I also started with the parent node, the left node with buttons and right node, but wasn't sure on how to continue tnx
@pine cape if you want to replicate a recourse bidirectional do i have to call commands.replicate_resource on both the server and the client?
Yes; although I've never tested this
alright i'll try it
it gives me this message ERROR lightyear::shared::replication::resources::receive: Only one entity per World should have a ReplicateResource<"mad_bevy_prototype::multiplayer::protocol::Players"> component
Hm i see; yes I don't think it's possible currently, let me open an issue
alright, then i'll have to find another solution for now tnx though
You could implement it manually by exchanging Messages
yeah that's what i did before 0.14 but i thought i'd try it like this
I tried manual with messages but it gives me an error that the message is not part of the protocol even tho i did added the message in the protocol plugin
What does your protocol look like?
app.add_message::<StartGame>(ChannelDirection::Bidirectional);
app.add_message::<JoinLobby>(ChannelDirection::ClientToServer);
app.add_message::<ExitLobby>(ChannelDirection::ClientToServer);
app.add_message::<Message1>(ChannelDirection::ClientToServer);
// inputs
app.add_plugins(InputPlugin::<Inputs>::default());
// components
app.register_component::<Transform>(ChannelDirection::ServerToClient);
app.add_prediction::<Transform>(ComponentSyncMode::Full);
app.add_interpolation::<Transform>(ComponentSyncMode::Full);
app.add_interpolation_fn::<Transform>(TransformLinearInterpolation::lerp);
app.register_component::<PlayerId>(ChannelDirection::ServerToClient);
app.add_prediction::<PlayerId>(ComponentSyncMode::Once);
app.add_interpolation::<PlayerId>(ComponentSyncMode::Once);
app.register_component::<ColorComponent>(ChannelDirection::ServerToClient);
app.add_prediction::<ColorComponent>(ComponentSyncMode::Once);
app.add_interpolation::<ColorComponent>(ComponentSyncMode::Once);
app.register_component::<TypeOfResource>(ChannelDirection::ServerToClient);
app.add_prediction::<TypeOfResource>(ComponentSyncMode::Once);
app.add_interpolation::<TypeOfResource>(ComponentSyncMode::Once);
app.register_component::<AmountOfResource>(ChannelDirection::ServerToClient);
app.add_prediction::<AmountOfResource>(ComponentSyncMode::Once);
app.add_interpolation::<AmountOfResource>(ComponentSyncMode::Once);
// resources
app.register_resource::<Lobbies>(ChannelDirection::ServerToClient);
app.register_resource::<Players>(ChannelDirection::ServerToClient);
// channels
app.add_channel::<Channel1>(ChannelSettings{
mode: ChannelMode::OrderedReliable(ReliableSettings::default()),
..default()
});
and you're unable to send the message?
yes i do this connection_manager.send_message::<Channel1, _>(Message1{ name }).unwrap(); when pressing the connect button and i says that the message is not part of the protocol
And you're using this to send from client-to-server right? I notice Message1 is ClientToServer.
In my lobby example I use bidirectinal messages so it should work
I updated to lightyear 0.14.1 and now I'm seeing a disconnect event but no connect event in host server mode, which seems weird
I've probably done something silly
But it feels like it shouldn't be able to disconnect without ever having been connected
No i think that's normal, it's because i start the app in Disconnected mode, so you're probably seeing logs that say "enter disconnected state"
indeed
Doesn't this need to be
connection_manager.send_message::<Channel1, Message1>(Message1{ name }).unwrap();
?
You can hide the second type because the compiler infers it from the message you're sending
@west garnet on the main branch, I enabled bidirectional resource replication. Also resource replication doesn't require the resource to be Clone anymore
And for send_message, you only need a reference now
Actually, there is no Clone bound required on any type of the protocol anymore (Message, Component, Resource) 🙂
alright cool
I feel like adding bidirectional replication (outside of input kinds of messages) screws a bit with server authoritative
Or I guess, that the networking library should push against users doing that at least a bit
Yes I don't think the bidirection replication is very polished right now, also the changes from bidirectional resource replication bypass change detection to avoid an infinite loop of updates
But it can be convenient to have
@pine cape can you tag 0.14.1 in git? I like to match my local git repo version with crates when debugging
done!
thanks!
If lightyear is replicating two copies of a server entity to a client instead of just one, is there anything obviously wrong I'm doing?
Hm actually nvm I don't think that's what's happening. I'll debug some more
It's really weird because I'm creating players on the server only, and two players appear there. But the client somehow gets four players, a duplicate for each. I'm only seeing two EntitySpawnEvent on the client, so I'm not sure where the extra two players are getting spawned
I'm most likely doing something very silly
The other two players that appear on the client are probably either the Predicted or Interpolated ones, they are spawned as separate entities
What is the idiomatic way to query if the current client has authority over an entity? I could match on client id and add a marker HasAuthority component myself, but this feels like something that lightyear might be already handling in some way?
Also sorry in advance for the coming avalanche of stupid questions but I'm finally fleshing out my networking
please keep the questions coming!
I don't really handle authority currently; that's something I want to do (https://github.com/cBournhonesque/lightyear/issues/95) but i'm not sure of the best way to do it.
I would suggest just adding a marker component PlayerId(ClientId) or HasAuthority on the entity
got it!
(btw, if you have client-prediction enabled; the player you have authority will probably be the one with With<Predicted>)
Is there a trick to get rust-analyzer to stop complaining about the vendor folder?
@pine cape zstd isnt in the latest release right?
no
Heyo, just came across this thread and been reading the docs. The concepts are still quite low level for me. Im completely new to game dev and would like to learn the concepts behind the ones described in lightyear, does anyone have additional resources i could read to understand more?
read gaffer on games blog imo
he has some good articles on networking
Awesome, thanks!
Ur welcome
This is also a very clear and concise explanation: https://www.gabrielgambetta.com/client-server-game-architecture.html
I am specifying interpolation_target: NetworkTarget::AllExceptSingle(client_id), but I'm still seeing the entity with an Interpolated component on the client. Is that expected?
Also, why do we specify on the server-side which clients are interpolating and predicting things? Isn't this a client concern?
right, so I'm still doing something funny
ah nvm I see what the problem was
Hm maybe you're right; the client could receive an entity, then there's logic on the client to add the prediction or interpolation
I guess you could do that now; listen for the ComponentInsertEvent<PlayerId> on the client, then decide based on the player_id if you want to add the ShouldBePredicted or ShouldBeInterpolated component on the client.
right, but this is unnecessary currently since lightyear figures this out anyway?
Yes, right now the server sends ShouldBePredicted or ShouldBeInterpolated as part of the replication message
but I like your approach
I could imagine this kind of thing be possible in bevy 0.14 once observers are available.
Something like
if player_id = my_player {
start_prediction
}
);
yeah, or it could just be a normal system that listens for Added<Replicate>
And then checks if player_id is present on the replicated entity? maybe, yes
haven't fully read up on observers yet so I'm not sure what the difference is tbh
It's basically a callback that is called when a certain ECS change happens.
What's nice is that it can listen for more fine-grained events, liked OnAdd<Replicate, PlayerId> fires only if both components are added at the same time.
(in your approach, we would listen for every new replication event and then check if the replicated entity has PlayerId, which could be more expensive)
Also the callback happens at the next apply_deferred instead of in a different system
I'm not sure if there are any situations where server should be the one knowing about prediction/interpolation; but I really like the idea. Created a ticket: https://github.com/cBournhonesque/lightyear/issues/315
was there any progress made on steam networking for the host-server use case? might look into that this week if not
ah no i haven't tried p2p steam networking; I can't install steam on my mac so it's not easy for me to test
i'll probably try to pick it up then!
as soon as i've solved some catastrophic physics bugs lol
I saw that renet uses the listen_socket_p2p api: https://github.com/lucaspoffo/renet/blob/master/renet_steam/src/server.rs#L43
it might be as simple as that
btw what sort of monitoring stack do you run when testing? i see you've put some effort into observability
I just update the env_filter in the log plugin to enable the logs I need (lightyear::client::prediction::rollback = trace), but nothing much more than that
got it, thought it might be something fancier
I wanted to log metrics to prometheus/grafana but there's a bug in bevy's LogPlugin that prevents me from doind that. Maybe in 0.14
yeah that would be neat
I tried to add a pbr mesh to the player entity on the client side by using commands.entity().add_child but is gave me warnings saying that the parent didn't have an inheritedVisability component. Then i tried adding the visibility components to the playerbundle and the protocol but they don't have the serialize trait. Is there another way to add a pbr mesh to the player entity on the client side?
I think you can just add a PbrBundle on the client side, which contains Visibility and InheritedVisibility
yeah i did that but then it gave the warnings about the player entity not having Visibility and InheritedVisibility
visibility should probably not be in the protocol since that just controls how the client renders things
You added https://github.com/cBournhonesque/bevy/blob/44e0da1e86c8edf1caa186a446e97711b4965198/crates/bevy_pbr/src/bundle.rs#L21-L21 ? I'm not sure why it would emit warning since Visibility and InheritedVisibility is part of the bundle
As martin said, visibility is probably purely on the client side
Is there any reason you want to add a pbr bundle as a child of something which isn't rendered? can you just add the bundle directly onto the entity?
ah right, that's the problem
yeah the problem is that the pbr mesh is a child of the playerbundle i made but i don't know how to add the pbr bundle directly onto the entity
commands.entity(my_player_entity).insert(PbrBundle { ... })
alright tnx i'll try that
It happens a lot with bevy UI. If you have Visibility on an entity the whole hierarchy is expected to have it. So by adding pbr bundle which contains Visibility as a child, if the parent entity doesn’t have it, results in that warning
what's the easiest way to inspect network stats? I see that there is a diagnostics plugin but it seems to be private
It should be public, I use it here for example: https://github.com/cBournhonesque/lightyear/blob/main/examples/priority/src/shared.rs#L53
is it possible to modify the Replicate network target after the fact? e.g. initially have All but change it to AllExceptSingle later on?
Hm i'm not 100% sure.
I think it would work if you removed the Replicate component and then re-added it with AllExceptSingle, but I don't think I handle updates right now
Another option could be to use rooms, and to remove that one client from the room
Ok, that makes sense. Wasn't sure if Replicate was set in stone once added. I think rooms are a better solution for that.
I accidentally added a link conditioner on the server io config instead of on the client, and this seems to make the client very jittery. Is this expected?
Putting the link conditioner on the client seems to work much better
actually no, things are very jittery still
It shouldn't make a difference, link conditioner works for the packets you receive, so if you put it on the server all the packets received by the server will have some delay/jitter/loss added
Things are jittery? What are you replicating, physics?
Yeah I'm not sure what I was doing because there now seems to be no difference between the two. I'm replicating physics with xpbd
Everything looks fine on the server but the client's predicted entity is very jittery. I'm not entirely sure how to debug. Now I'm just trying to go over every line comparing with the examples
You can also refer to this example: https://github.com/panjeet/networked_cube_test
Some common issues:
- does it look good with one player only?
- ordering of inputs and physics (input handling should come before the physics update)
- the leafwing input data shouldn't be replicated back from the server to the client
- are all the components that impact the position present in the ComponentProtocol with prediction enabled?
- is client -> server replication enabled if you use pre-predicted entities?
So I have one host-server and a client. Host-server works perfectly no matter what (which is unsurprising I guess), although I never tried putting link conditioning on the host-server's client plugin. Adding a remote client makes it break down on that client
I will try verifying the things you mentioned
Is this an example that is supposed to be broken?
@bronze elm made the example; it was broken, but I helped him fix it. It should just work now
I explained my debugging approach here: https://github.com/panjeet/networked_cube_test/pull/1
I will suggest maybe only syncing Position and Rotation and not Transform, because xpbd itself syncs those, and trying to replicate Transform causes some strange issues. The jitter can be reduced by tweaking the system order (or you can do something weird like I did and interpolate the player back to themselves)
@bronze elm you shouldn't have any jitter even with prediction no? (in the example). It worked for me
I can’t remember. I think your PR fixed it but when I tried reducing the amount of latency you added it came back
I have an issue where my incremental compilation is semi-broken, every time i run it recompiles ~50 packages, which is super annoying. Do you also have this issue?
I noticed that recently, really slows down iteration.
yes, it's infuriating
Looks like bevy and wtransport are being recompiled. Maybe it's because of the wtransport upgrade? I will try to bisect it
@peak nymph does it recompile the same packages for you as well?
Mine usually starts with
Compiling objc_exception v0.1.2
Compiling blake3 v1.5.1
Compiling ring v0.17.8
Compiling zstd-sys v2.0.10+zstd.1.5.6
Compiling objc v0.2.7
I've had this issue in the past due to a broken rust-analyzer version I think
my jittering issue is almost certainly a system ordering problem
just need to figure out what is misordered
I fixed my incremental compilation issue... I had some compiler env variables in a tmux tab that were causing recompilations..
Mine may be a tmux env var issue too, will have to check. I know I had some stuff set while doing some profiling a while back.
But yes I did notice those packages being rebuilt every time. Ring was definitely on the list
@pine cape i figuerd out why i couldn't send the message. I send the message in the same frame as the client.connect(). So there was no connection yet to send the message
Ah i see!
Yes you can use the run_condition is_connected to check if the client is connected. Or check if the NetworkingState::Connected
yeah that is what i'm doing now 🙂
Ultimately you have to sync position to the transform translation though right? Where do you do this so as not to conflict with xpbd?
I think xpbd already syncs positions to transforms
hmm, isn't position a custom type? https://github.com/cBournhonesque/lightyear/blob/main/examples/interest_management/src/protocol.rs#L77-L78
not in the xpbd example: https://github.com/cBournhonesque/lightyear/blob/main/examples/leafwing_inputs/src/protocol.rs#L180
This ordering in particular is important: https://github.com/cBournhonesque/lightyear/blob/a5a3f7b14bf905af353eb56d0d422ada1785ed7c/examples/leafwing_inputs/src/shared.rs#L76-L76
I just merged 2 big changes (cc @floral meteor @dull lion )
- I removed the
enable_send: bool,
enable_receive: bool
}
It was easy to misuse. The original goal was to enable/disable the replications systems at runtime, but instead it was used to not spawn replication-send systems on the client side to save CPU.
Instead, the ClientPlugin and ServerPlugin have been replaced with ClientPlugins/ServerPlugins which are PluginGroups. This means that you can freely disable any of the subplugins in lightyear (Rooms, ReplicationSend, ReplicationReceive, Prediction, etc.)
All the plugins are enabled by default, but users who don't want to use a subset of features can just disable the corresponding plugin. (PR)
- I updated all examples to use a common test harness. (PR)
Hopefully this should make the examples clearer to read. All the complicated code destined to showcase the multiple modes (ListenServer, HostServer, etc.) and transports (WebTransport, Steam, etc.) now lives in a separatecommoncrate. The example folder only contains the code that is actually relevant to the example
great job!
Am I supposed to have an ActionState on both predicted/interpolated and confirmed entities, just one of them, or does it not matter?
The ActionState should be on the predicted entity
FixedUpdate can run zero times in a frame right? So it's possible for lightyear to replicate a Position to a client, and then FixedUpdate doesn't run that frame, and so Transform is not synced during that frame (if xpbd is running in FixedUpdate)
Or am I mistaken
I think that sounds right
Yes that's correct
Okay I think I've worked out my transform sync issues. Now I'm trying to work out why prediction is not working the way I expect it to.
I have a stupid character controller which just applies some linear velocity in the direction I press. Here I'm just holding down the up key, and the confirmed entity (in red) seems to settle in the ceiling which is what I would expect, but the predicted entity (green) is getting pushed down by gravity each frame, never reaching the ceiling.
The ActionState of the predicted entity consistently shows that the up key is being held, and I've copied the FixedSet::Main/Physics split from the leafwing example, applying inputs in FixedSet::Main. So I'm somewhat confused as to why it seems to consistently predict downward motion
The confirmed entity seems to consistently have a small amount of downward velocity. Since the predicted entity is a few frames ahead, I guess this small error then gets magnified
So maybe this is more of a problem of 1) my character controller is dumb and 2) bevy_xpbd doesn't reach an equilibrium here
Cool art!
Maybe you can invite me to your repo privately so i can take a look? I can't immediately tell right now
I invited you! It might crash and burn, as it's not very well tested on other computers 😄 I don't expect you to fix my code for me but feel free to have a look
VisualInterpolationPlugin seems to create tons of rollbacks even if the previous value is very close to the current value. Perhaps it makes more sense to instead let the user provide some approximate equality and check that |current - previous| > epsilon
might also be useful for rollback in general?
Yes, I think letting the user define a comparison function could be useful
and due to orphan rules this has to be a function pointer somewhere
yes, unfortunately
VisualInterpolation should not contribute to rollbacks, because in PreUpdate we restore the actual value of the component
it does affect rollbacks for me though
hm i see, there's an ordering missing. The component restore should happen before the rollback check
For some reason I cannot observe this in the replication_groups example, where I have prediction + visual interpolation enabled. (i.e. I don't see any rollbacks)
I'll still do the ordering change, I would have liked to be able to confirm the behaviour though
does not seem to have solved the issue for me. i'll have a look
Could it be your position quantization scheme?
Is lightyear constantly replicating every entity or does it only replicate change in entities or how does that work?
It only replicates entities that have the Replicate component and it constantly replicates updates for that entity's components that are present in the ComponentRegistry
it only affects transforms, not Positions
@pine cape I think you flipped the condition here: https://github.com/cBournhonesque/lightyear/commit/314365384a60e0a13dc096cf752dea6b2040a6d0#diff-876847cc39f7da6ae330e433a3f225466169f154512374698429bf8d53e23935R179. Getting some really weird results when enabling rollback checks!
The conditions should be correct, rollback_check_fn is basically PartialEq::eq;
it seems to work for me in the examples
It should return true if you should roll back, no? which would be the opposite of equality
hm maybe i should change the name to equality_fn, the function you pass in should just be is_equal?
it should return true if no rollback is required
yeah i think that's a better name
the good news is that it does seem to reduce a bit of rollback noise for me
But tbh there really shouldn't be any rollbacks with only 1 player
only one remote player you mean?
yes
hmm yeah. food for thought 😅
the client should more or less do a perfect prediction since it has all the same physics rules as the server
it does predict physics perfectly for me if i don't provide any inputs, and just let my character bounce down through the cave
So I'm running some logging in FixedPostUpdate.
- On Tick(327), I start moving right. But the
LinearVelocitycomponent on my player is still 0, and it remains 0 for many ticks after that. - Lightyear starts a rollback on Tick(328), but that is one tick after I started moving. If it wants to roll back I think it should start the rollback on Tick(327)
- After the rollback it resumes on Tick(329) with a non-zero LinearVelocity
Ah damn it, pressed enter too early
I think the effect of this is that it skips two ticks though, because I was moving right on tick 327 and 328 but it only starts registering the new velocity on the rolled-back tick 329
Can you show me the actual logs?
Lightyear starts rolling back one tick after, because on the tick of the rollback check it already sets the correct component value, so there's no need to rollback from that tick again.
The tick where it actually starts the rollback is the one from the line: https://github.com/cBournhonesque/lightyear/blob/5239752b40f4da9ad56346c434d6a144a0403977/lightyear/src/client/prediction/rollback.rs#L185
when is your player_sim log? In FixedUpdate after the FixedSet::Main? Or after the movement system?
In FixedPostUpdate, after player inputs
really just doing this:
fn debug_stuff(
tick_manager: Res<TickManager>,
rollback: Option<Res<Rollback>>,
player_q: Query<
(&LinearVelocity, &PlayerSlot, &ActionState<PlayerAction>),
(With<Player>, With<Predicted>),
>,
) {
let tick = rollback.map_or(tick_manager.tick(), |r| {
tick_manager.tick_or_rollback_tick(r.as_ref())
});
for (linvel, slot, action) in player_q.iter() {
if slot != &PlayerSlot(1) {
continue;
}
info!(
"tick: {:?}, player vel: {:?}, moving right: {:?}, moving left: {:?}, ",
tick,
linvel,
action.pressed(&PlayerAction::MoveRight),
action.pressed(&PlayerAction::MoveLeft),
);
}
}
I'm really having a difficult time understanding protocols
I don't really understand what the protocol function is and it has a red outline whenever I try to use the protocolize!
Are you using the latest version? the protocolize! macro doesn't exist anymore
this is how you define your protocol now: https://cbournhonesque.github.io/lightyear/book/tutorial/setup.html#defining-a-protocol
Finally getting around to checking my env but not seeing anything obvious. Which env vars did you have set?
I had AR and CC set
okay I have those pointing at a homebrew install of clang, didn't realize it could impact which packages get recompiled
kind of a random idea... what if there was a way to configure replication (sort of like how replicate_once works) so that you could add Replicate to a component and have it spawn client side, but otherwise doesn't get any updates, but then the server could trigger a replication later on.... Right now the choice for something that's "request-response" is 100% message, or client initiated message with server replicating back, but the message route the convenience of having stuff magically sync is lost and with replication packets are sent all the time and trigger ComponentUpdateEvent when there are no changes. Also messages don't respect rooms, so that would be another advantage of this method following interest management out of the box
Does lightyear potentially work with physics?
Yes it does, check https://github.com/cBournhonesque/lightyear/tree/main/examples/leafwing_inputs for a 2d example with xpbd, and https://github.com/panjeet/networked_cube_test for a 3d example with xpbd and tnua
I'm not sure I understand what you said.
You want a more convenient way to send messages from server->client? You could create an entity with a component, and update that component every time you want to send a message.
I might add a send_message_to_room function if you think it could be useful
Ah you want a more convenient way of having a C->S->C pattern (request, then response)?
Yes which is what I've tried, but it constantly is replicated even when updates are rare, so there is no easy way to handle the update event detection on the client
Also it would be great to be able to send a message to a room, the client hashset is not public so you have to do something like this
let client_ids: Vec<ClientId> = server_global
.client_id_to_entity_id
.clone()
.into_keys()
.filter(|client| room_manager.has_client_id(client, room_id))
.collect::<Vec<ClientId>>();
which is not ideal when room already has the list 😄
I don't think it's constantly replicated, it should only be replicated when the component has an update.
But yes there are some issues:
- replication updates always trigger change detection, even when the component is the same. (I could prevent this but it would required adding a
PartialEqbound on the component) - we keep sending an update until we have received an ACK from the remote. issue This means that we potentially send the same update many times, because it takes time (1 RTT) to receive an ACK. This is probably why you're seeing 'constant replications' even when you do a single update. I plan to fix this
oh, that makes sense... is there any workaround to the first one? the event only has the entity so i dont know how I would compare them
You're not doing bidirectional replication using a same entity, right? you're using 2 entities?
That would trigger an infinite loop of replication, since every message received triggers change detection, which triggers a new replication send
I don't know if it would be useful, but you could use bidirectional Resource replication? I've disabled change detection for bidirectional resource replication to avoid this specific problem.
eh... a simple analogy to illustrate is say you had a light in a room, it rarely changes state, but if a player clicks on a switch (i.e. client sends message saying im clicking on an object), the server changes the light state, for all players in the viscinity that get that replicated update, say i want to take that boolean and do something with it only when that state changes
Which client hashset is not public? I could make it public
I think you can do this with Message currently
Yeah the only reason I'm trying not to is that entity is already using rooms, so I would have to handle spawning and despawning myself with messages
i'm not sure what the exact usecase is, but you could try just moving entities in/out of rooms? that will make the server send spawn/despawn replication updates
I guess this would very nearly work, the only thing I can't detect is when there is an actual change on the client side
Are you saying to do this?
room_manager.remove_entity(entity, room_id);
room_manager.add_entity(entity, room_id);
maybe you could send me a code example in private? I have a hard time understanding
I'm looking at the steam integration now, and there are a few things I want to change:
- I want to run
run_callbacksoutside of lightyear, throughbevy_steamworks. - I also want to start the steamworks client outside of lightyear (also via
bevy_steamworks) - I don't think I need a
steamworks::Serverfor P2P connections, so this would have to be done separately for dedicated servers
And probably more stuff as I work on it!
I can upstream these changes to lightyear, or I'll fork the steam integration and do the changes myself
Also, is this really safe https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/connection/steam/mod.rs#L11 given that SingleClient explicitly contains a _not_sync PhantomData field?
I'll fork the steam integration and do the changes myself
This is maybe not even possible since there is a match statement in connection/client.rs which links NetConfigs to implementations
This now feels like a slightly bigger refactor than I was hoping for... To use the steam client provided by bevy_steamworks, I'd have to access it through the ECS resource it provides. However, it seems that lightyear expects client networking functionality to be fully contained within the ClientConnection struct and there's no way to pass in additional data from the ECS
From the looks of it, this is handled in Renet by registering send/receive systems from the steam plugin which looks nicer on the surface level since it 1) allows transport plugins to be provided from outside of the library 2) allows arbitrary ECS queries to be run in the plugin's systems
You want to change this for the p2p usecase, or just in general?
in general, though i'm mainly interested in p2p
it just feels like this shouldn't be in lightyear at all since other steam feature like e.g. achievements have nothing to do with networking
I wanted them to be in lightyear to have an easy hands-off integration without having to worry about calling the callbacks, etc. yourself.
I think we could just add some fields run_callbacks: bool and client: Option<steamworks::Clien> in the SteamConfig. If no client is provided, we create our own
In which case you could just use the client however you want outside of lightyear
sure, that's alright with me. but it still seems a little bit awkward to pass in a Client/SingleClient into the NetClient since it has no ECS access
But yes, I acknowledge that having the ClientConnection hide the internals behind a trait object can be annoying
Apparently we only need a reference to the client (https://github.com/lucaspoffo/renet/blob/master/renet_steam/src/client.rs#L21)
so you could put the Client/SingleClient in a resource, and then pass it in as a reference to the SteamConfig
This way you would still get access to it via the ECS
i'll think about it
I don't think that works for SingleClient though, since it is not sync
SingleClient should actually be sync: https://github.com/Noxime/steamworks-rs/issues/159
https://partner.steamgames.com/doc/api/steam_api#SteamAPI_RunCallbacks According to Steam, RunCallbacks is actually thread safe to call from multiple threads if need be, so SingleClient may be Sync...
Hmm interesting
I opened a PR to kill it. We'll see if it goes anywhere: https://github.com/Noxime/steamworks-rs/pull/179
nice! maybe james will take a look, I think he has push access to that repo
I wound up just sending a message back as a notification that something has changed, but I do thing it would be helpful if the client hashset in rooms was pub instead of having to iterate all clients and check if they are in the room. I think that's good enoguh for sending a message to a room since you can just use NetworkTarget::Only which takes a vec of client ids
I made it pub in main 🙂
I was copying one of the examples and using the component insert event and was only getting an empty value in the context of the event as opposed to the value
Thanks, works great. The new helper method is nice
Update on rust in rust... implemented inventories using lightyear replication + interest management. Each inventory is spawned server side and put into its own room, and the client is enjoined to allow it to update. This way if multiple players are looting the same crate for example they will all get updates. Then on front end whenever the inventory updates it gets synced to a separate set of UI components
The context is actually empty on the client. The event just tells you that the component got inserted; to query it you need to query it in the ECS directly
Nice! I also added a more direct VisibilityManager that can give more control than the RoomManager
oh alright thank you
oh nvm I was completing wrong about what was wrong apparently it's erroring that the ComponentInsertEvent doesn't exist in resources or something of that sort
Resource requested by server::network::replicate_players does not exist: bevy_ecs::event::Events<lightyear::shared::events::components::ComponentInsertEvent<common::player::PlayerId>>
Did you add the PlayerId component to your protocol? With the correct direction?
I am so confused. I am getting this error message right after initializing the steam client:
2024-05-12T15:38:12.397021Z INFO lightyear::connection::steam::server: Steam client initialized
[S_API FAIL] Tried to access Steam interface SteamNetworkingSockets012 before SteamAPI_Init succeeded.
The renet example doesn't do anything apart from calling steamworks::Client::init_app which is what I'm doing too
and yet the renet example does not seem to crash 🤔
client.networking_sockets() is not null right after initializing the client, but is null when the server is starting
In your branch, or in main?
I think it would still be useful, so that I can look at the changes
Gonna have some food and then i’ll be back
Well I think so I set it to bidirectional actually the main thing I've been noticing now is that when I create a entity on the client and replicate it to the server the server Errors and says cannont find entity? Is there some special setting I have to enable for this to work?
Are you using main or 0.13?
You have to turn on enable_send on the ReplicationConfig: https://docs.rs/lightyear/latest/lightyear/client/replication/struct.ReplicationConfig.html
API documentation for the Rust ReplicationConfig struct in crate lightyear.
I'm using whatever the most up to date crate version is
For client->server replication, you need to enable it in the ReplicationConfig for the client and the server; it's not enabled by default (because 95% of cases just need server->client replication)
Ah, I think the problem is that the steam client gets initialized twice somehow
Perhaps the server should have a similar static OnceLock thing as the client to prevent accidents
but I should figure out where the second call is coming from
Indeed it looks like we create the server both when adding the plugin and on start:
// Stack trace of first creation
lightyear::connection::steam::server::Server::new
lightyear::connection::server::NetConfig::build_server
lightyear::connection::server::ServerConnections::new
lightyear::server::networking::rebuild_server_connections
bevy_ecs::system::system::RunSystemOnce::run_system_once
<lightyear::server::networking::ServerNetworkingPlugin as bevy_app::plugin::Plugin>::build
bevy_app::app::App::add_boxed_plugin
bevy_app::plugin_group::PluginGroupBuilder::finish
// Stack trace of second creation
lightyear::connection::steam::server::Server::new
lightyear::connection::server::NetConfig::build_server
lightyear::connection::server::ServerConnections::new
lightyear::server::networking::rebuild_server_connections
lightyear::server::networking::on_start
@pine cape can this be removed? https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/server/networking.rs#L76-L79
I don't think this can be removed currently; but in your branch the steamworks::Client would be contained in a separate Resource, no?
I removed the steam client resource logic thinking I could skip it for now, but now I'm back trying to refactor the steam client logic again
I'll try to make server creation possible to run multiple times like the client
If you're blocked on some refactoring or some config thing, don't worry too much about it.
As long as the core p2p part works I can move things around afterwards
Okay, I pushed a change which fixes the issue for me. I can now create the steamworks client outside of lightyear and pass it in
I used Arc<RwLock<...>> which I guess should actually be unnecessary if the single client is actually sync
But this seemed like an easy way around the problem for now
The old behavior should be preserved since you could just pass None into the steamworks_client field, but I actually think this is a little bit ugly and the option should maybe be removed entirely?
This should also allow client and server to use the same steam client in host-server mode
Maybe, yes, if it's easy for the user to create their own steam client
I could add a function to create a default, without needing to add steamworks as a dep to your project
Or wait, this is already in the pr
I think I need to wait for my steamworks account to be approved before I can make any further progress
Sounds good, thanks for the work so far!
I'm getting an InvalidHandle error both in lightyear and renet which I'm assuming is because my account is not approved
yes I had to wait for my steamworks account to be approved as well
Just curious how easy it would be to switch from not using steamworks to using steamworks?
Very easy. All the examples already are compatible with steamworks by default; you just need to uncomment the lines
Instead of providing a NetConfig::Netcode, you just need to provide a NetConfing::Steam here: https://docs.rs/lightyear/latest/lightyear/prelude/server/enum.NetConfig.html
Configuration for the server connection
Oh sweet that’s great
If you want i could also test anything in the meantime, mine is approved and everything :)
And i also planned to work on some steam Stuff in lightyear, i'm just currently really busy
I've merged a big PR that introduces a number of changes:
Replicateis not aComponentanymore. It is now aBundleof multiple smaller components that each control an aspect of replication:ReplicationTargetto specify who the entity should be replicated toVisibilityModeto enable interest managementControlledByso the server can track which entity is owned by each clientReplicationGroupto know which entity updates should be sent together in the same messageReplicateHierarchyto control if the children of an entity should also be replicatedDisabledComponent<C>to disable replication for a specific componentReplicateOnceComponent<C>to specify that some components should not replicate updates, only inserts/removalsOverrideTargetComponent<C>to override the replication target for a specific component
- Before, the
Replicatecomponent was created once and could not be updated after creation. It is now possible to update these replication components at runtime (for example to update theReplicationTargetof an entity) - Added 20+ tests around the replication logic, so hopefully everything should be air-tight
There's a couple of other things I want to fix (Issue1, Issue2), and then i'll probably push a new release.
This will be the last release before bevy 0.14
good stuff!
Hmm, I'm trying to use leafwing's just_pressed in FixedUpdate but it is missing key presses. This does not happen when running the same system in Update. Is this something you have encountered?
I wish I could search threads on discord
Might be a known issue: #1034547742262951966 message and https://github.com/Leafwing-Studios/leafwing-input-manager/issues/252
yes, it's something I've encountered, I wish I had a better solution for it
I think the solution will be to have better support from Leafwing (for example also run leafwing systems in FixedUpdate)
Take a look at the comment here: https://github.com/cBournhonesque/lightyear/blob/d8942e5a2a272afded1ee751fd7b170cdb70d056/examples/bullet_prespawn/src/shared.rs#L238
haha // NOTE: pressed lets you shoot many bullets, which can be cool silver lining
should I use the lightyear::client::input::InputSystemSet::BufferInputs system set with the leafwing plugin too?
consuming the action and using pressed does seem to work
not sure if there are any caveats here
Pre-requisite knowledge:
- JustPressed becomes Pressed in a system that runs in PreUpdate (so once-per-frame)
- lightyear handles inputs in FixedUpdate, to have access to tick information
Basically the problem is that:
- if you use
just_pressedinFixedUpdate: you can have frames where FixedUpdate doesn't run, so the JustPressed becomes Pressed andjust_pressed()misses the input - if you use
just_pressedinUpdate: I actually cannot remember the exact problem here.. I think it's that the tick-number inUpdateis not reliable. There's no guarantee that it would be the same as in the server, so you might get off-by-1 errors.
The solution to use pressed in FixedUpdate and consume the action. I think it works, but it generates more diffs than necessary (wasted bandwidth) because of some leafwing issue
But basically this is a huge footgun! Maybe i'll push a fix to leafwing directly, as this is super important
it's certainly quite confusing
I don't think that's required anymore. (it used to be in bevy 0.12 because there was only the FixedUpdate schedule, but now that FixedPreUpdate got introduced I think it's not needed anymore). I'll open an issue to look into removing these
hm, so should input handling be in FixedPreUpdate or FixedUpdate? the examples use both
- without leafwing: you have to buffer the inputs manually yourself, and it should be done in FixedPreUpdate (in
BufferClientInputs). - with leafwing: you don't need to buffer inputs manually yourself, it's done for you in FixedPreUpdate.
In both cases you should consume the inputs in FixedUpdate
@stiff dome didnt see u respond to https://github.com/cBournhonesque/lightyear/issues/144 before, but thanks for the examples
works flawlessly with tab switching
question is now how to actually integrate this
@stiff dome do you know what wasm-bindgen-futures exactly does for spawn_local?
because to me it doesnt seem like it uses promises
async timer seems to just use setTimeout so that might be whats keeping it alive
but prob not
huh
oh yea nvm
just didnt look for the right stuff
@pine cape so yea promises seems to work and maybe this weird queueMicrotask might also be an interesting thing to explore
i'm out of my depth here haha, I don't really understand what you're saying.
The xwt examples work cross-tab, so how did you identify that requestAnimationFramework is causing the issue in lightyear?
What is the difference between the xtw examples and the lightyear examples?
It uses wasm_bindgen_futures::local directly instead of IoTaskPool::spawn_local?
ok so
requestAnimationFrame apparently stop running shortly after switching tabs (unless you disable ram saving i think)
bevy uses rAF to run its systems and pools
including the io pool
meaning it will stop running shortly after the user switches tabs
are there docs about bevy using the rAF?
idt so no
its just the main way of using vsync in the browser
everything else doesnt really work and causes frame lags and so on
I have this branch that replaces BevyIoTaskPool with wasm_bindgen_futures: https://github.com/cBournhonesque/lightyear/pull/352
The client still gets disconnected on tab change (testing with simple_box)
yea, meaning it will keep the tab alive using promises and queueMicrotask (i think so at least, maybe u dont really need one of them)
interesting
working demo, couldnt get the example to run with trunk directly
u would need to replace the cert with the one generated by the server tho
Yea I trust that the xwt examples work, but in lightyear I just get this error Error(JsValue(WebTransportError: Connection lost.Error: Connection lost.))
this works with ram saving too btw
on websocket, i get client connection timed out from the netcode side, which means that we have stopped receiving keepalives
yea there is no way thats right
thanks for testing the xwt examples! at least we're making progress
yea
i wish i saw mozgiis reply earlier
:x
hm @pine cape there might be a possibility that tokio is actually at fault here
prob not but its like one of the few differences between the xwt example and this
I don't use tokio in wasm though, apart from their oneshot channel
i mean the tokio::select
although
hm
there is another possiblity that might be more annoying to fix
ah maybe; i can replace it with a futures::select
what happens if the packet recv channel keeps on receiving but not getting cleared because raf/bevy isnt running
lightyear might think the client is timeouted because it doesnt send back keep alives
yes i guess it doesn't matter if I use wasm_bindgen_futures
the problem is still that the bevy app stops running because of RAF
so we are not sending back packets
yea
welp that sucks
i have no idea how to even approach fixing that
running bevys main loop independant of RAF doesnt make sense
maybe having netcode outside of raf
I could have a separate thread in wasm_bindgen_futures::spawn_local
that keeps returning keepalives
the client would need a complete resync after switching back to the tab tho
[Violation] 'requestAnimationFrame' handler took 52ms
WebTransport connection closed. Reason: Err(JsValue(WebTransportError: Connection lost.Error: Connection lost.))
I tried disabling Energy Saver and Memory saver on chrome but i still get disconnected
u can ignore that usually
as long as it doesnt happen all the time
idk, maybe brave does raf differently or its a hardware thing idk. but if i disable mem save then the tab only desyncs after like 5 secs or so
if not it does instantly
actually weird that I got a client connection timed out from netcode in the websockets case
since from our understanding the bevy threads shouldn't be running at all?
yea
raf basically works like this:
yourfunc() = {
callbevymainloop();
raf(yourfunc)
}
to start:
raf(yourfunc)
raf will just indefinetly wait to run yourfunc again while ur not on the tab
I see, so when I come back to the tab, netcode logic runs again, it sees that the last packet receives was 5 seconds ago and errors with timed out
i guess so
So i guess solutions are:
- run a separate thread outside of BevyIoTaskPool that keeps receiving packets and sends keepalives. When we're back, send all the buffered received packets to the bevy task (i.e. the bevy task reads from the channel)
- put all of netcode outside of bevy systems? seems hard
- play audio (needs to be tested)
seems logical
imo 1 is prob best solution
since we only need this on the web client, we could also just do this entirely in js
altough prob not worth it
since xwt does the js part for us already
not worth it, the keepalive packets need to be encrypted and the whole logic is already in rust
ah alr
so i think only recv channel needs to be unbounded right
yea I think sending just sends keepalives, since we have nothing to send
recv would probably be bounded in practice; we don't want to buffer an infinite number of packets
but honestly the best solution would just be for bevy to be able to keep running in the background
I think we can decide
ok
seems unrealistic given bevy would generate frames without a target during that period (if rendering is enabled)
hm
maybe lets make an issue for that then
i wonder if request idle callback still runs in background
I'm not knowledgeable enough for that, i still don't know what the RAF is. I asked a question in #web
might be a headless alternative to that
this seems relevant: #web message
I don't think 1) is even a good solution, because you would have to process potentiall 1000s of frames at once on the client when you connect the tab again
@pine cape is this universal or just for that example?
https://github.com/cBournhonesque/lightyear/blob/1475df6d87facc738306e849fac8d2846e5a77f8/examples/leafwing_inputs/src/protocol.rs#L52
universal.
If you have 2 predicted entities that are in different server ticks (because you receive their updates in separate messages):
- E1 in tick 12
- E2 in tick 14
And you notice a rollback for the one at tick 14 (because the value is different from the predicted history)
Then we need to rollback everything.
But we cannot rollback from tick 14, since we don't know yet what the value of E1 in tick 14 is
like lets say we have two players each have a child, do all entities need to have the same group
In practice it's not a problem since you only need predict your own entities. (so there's not that many entities that will be predicted/in the same replication-group)
yea that makes sense
i was thinking abt this
https://github.com/cBournhonesque/lightyear/issues/130
which would necessitate predicting other players entities i think
might be totally off idrk
I guess you could actually rollback from the oldest received tick of any predicted entity, which would be tick 12
This means that for predicted entities, we need to store the history of all server received ticks (up to the oldest received tick of all predicted entities). i.e. for E2 we also store what the state at tick 12 was
but it could be problematic..
What if the update for E2-tick12 hasn't arrived yet? or is lost? then I guess you cannot rollback properly
but maybe this can be an alternative to be investigated
Lag compensation is different, it's a server-side thing (whereas prediction is client-side): https://developer.valvesoftware.com/wiki/Lag_Compensation
oh ig i misunderstood :v
Could be convenient to have a ComponentUpsertEvent to have one event type for all modifications to a component, what do you think @Periwink?
I think I will remove those events as soon as bevy 0.14 lands, as it will be more appropriate to use observers to react to them
|Observer<OnAdd, (C)>, Query<(&C), With<Replicated>>|
(so that i don't have to emit events everytime even though they are not consumed, which wastes CPU for nothing)
We only have OnAdd OnRemove OnUpdate observers; I think OnUpdate is what you call upsert
got it, makes sense
yea that makes sense
Is this necessary to add to the player given that only bullets are pre-predicted? https://github.com/cBournhonesque/lightyear/blob/1475df6d87facc738306e849fac8d2846e5a77f8/examples/bullet_prespawn/src/server.rs#L98
nope, probably can be removed
it looks like players are also pre-predicted
bullets are pre-spawned 🙂 confusing i know
Hm, is there a difference between pre-spawning and pre-prediction?
Pre-prediction is client-induced: the client spawns an entity, then replicated it to the server, which takes authority.
Pre-spawning: a shared system runs on both client and server (for example 'spawn bullets'). It will run first on the client (since it's a bit in the future). When the server spawns the bullet and replicates it to the client, it will use a hash to find the existing pre-spawned bullet on the client and use it as its target.
or if you look at https://docs.unity3d.com/Packages/[email protected]/manual/ghost-spawning.html#different-type-of-spawning
- pre-prediction = Predicted spawning for the client predicted player object
- pre-spawning = Predicted spawning for player spawned objects
ah, alright!
@dull lion how is it going with the steam p2p changes?
If there is anything i could take up, just let me know. Would gladly help. Got some time atm
i'm not working on it at the moment as i'm still waiting on steam to get back to me. I implemented the changes I think are necessary in the draft pr, except for tests/docs etc
feel free to test the pr in your game
maybe it works, maybe not!
That sounds great!
I'm at a Stage with my Project right now where i Plan to Experiment with multiplayer and wanted to use lightyear. So will definitely do that!
thanks for the identity fix @dull lion 🙂
I'll release 0.15 now
Released! #crates message
Awesome 🎉
@pine cape i see that the settings.ron isnt really a lightyear thing, and more something you built for the examples.
I think it would be really cool to have that directly in lightyear, so anyone could use it. maybe we can even add hot reloading, allowing us to change config at runtime.
opinions?
Hm i'm a bit hesitant to include something like this that is pretty independent from the core of lightyear.
There are probably some dedicated hot-reloading crates that could handle this better
yea seems super out of scope
Working on implementing lightyear into my project.
Whenever i connect to the server, i see in the console a ERROR: send connect event to server
i can see in the source code that this isn't really an error, but rather just a normal message?
just wondering why we're outputting it as an error
Probably just a leftover debug log
yea i figured. was just very confusing as a first time user, thought something didnt work
i want a certain component cloned from client to server, do i have to insert that component on the client side or the server side?
and does it automaticly get cloned when it gets inserted?
You have to insert the component on an entity on the client side, and also add the replicate bundle on the entity.
Like this: https://github.com/cBournhonesque/lightyear/blob/main/examples/client_replication/src/protocol.rs#L24
i spawn the player bundle on the server because al the other components need to be server to client, does it work if i do commands.entity(entity).insert(Component) on the cloned entity that is on the client side?
So you want:
- to replicate an entity from server to client
- but to replicate one specific component from the client entity to the server?
yes
I think the easiest might be to just send a message from client to server, and when the server receives it you insert the component
Otherwise I haven't tested it, but you might be able to:
- add
server::Replicateon the server_entity S to start replicating to the client - then add
client::Replicateon the client_entity C to start replicating to the client. You would need to include DisabledComponent for all the other components to avoid their replication. - then you can add the special component TargetEntity to specify that you don't want to spawn a new entity on the server, but re-use the existing entity S.
- On the client, you have access to the entity S via
ConnectionManager.replication_receiver.remote_entity_map
Two things come to mind:
- are you sure you need this? it is a bit unusual. What is your use-case?
- I think I might need a more expressive way of specifying which components need to be replicated. There's no way to specify
ReplicateOnly<C>to only replicate a single component
If this component is present, we won’t replicate the component
Defines the target entity for the replication.
alright, i think i'll have to rethink my structure, i need i specific transform form a client side entity (that doesn't exist on the server) when i press a button. I tried doing this with a message but the message with the transform information doesn't come in time for when the input is processed.
mut input_reader: EventReader<InputEvent<Inputs>>,
mut message_reader: EventReader<MessageEvent<PlayerTransform>>,
mut commands: Commands,
player_query: Query<(Entity, &PlayerId), With<PlayerMarker>>,
weapon_query: Query<(&Size, &Transform), With<WeaponMarker>>,
){
for (entity, player_id) in player_query.iter(){
for input in input_reader.read(){
if let Some(input) = input.input(){
match input {
Inputs::Build(building) => {
if building.building {
info!("B is by client");
for event in message_reader.read(){
info!("message is binnen");
let client_id = event.context();
if &PlayerId(*client_id) == player_id {
let replication = Replicate {
prediction_target: NetworkTarget::Single(*client_id),
interpolation_target: NetworkTarget::AllExceptSingle(*client_id),
// replication_mode: ReplicationMode::Room,
..default()
};
let transform = event.message().player_transform;
let duration = 3.0;
let health = 100;
info!("weapon build");
//spawn entity commands.entity(entity).add_child(child);
commands.entity(entity).insert(WeaponBuildMarker);
}}}}
_ => {}}}}}
}```
the first info!() with b is, always triggers when i press b, but the info!() after that doesn't always trigger and that would mean that there is no message but i send the message when i press b
You want to spawn a weapon on the client when a button is pressed, but the server should be aware of the weapon's transform?
I think messages might be the easiest route as Periwink mentioned. Whenever you run into the case of “request-response” it’s easiest to just send a message, because you could trigger the message client side from the input, send the message containing the transform at the time it happened. And then server side optionally validate / sanity check that transform (maybe some distance check and what not) and then have the server insert a component which will replicate it back to the client
oh could it work if i discard the whole input part and just place the weapon when the message comes in?
and then just send the message when i press the b key
In this case it might make sense to do pre-spawning like in here:
- you run a system on both client and server to spawn a weapon when an input is pressed
- it will run first on the client timeline, since the client runs a bit in the future (predicted)
- then it will run on the server to spawn the weapon there as well
- then the server will get replication authority over the client entity
Otherwise, yes just what panjeet said: listen for input event on client. When an input comes, spawn the weapon on the client, and send a message to the server with the transform of the weapon
But it would be easier if you described the high-level thing that you're trying to achieve; is it spawning a weapon on the client? Would the server have authority over the weapon?
tnx i think i can work with this, i'll take a look at pre-spawning
On a related note, when I handle client side inputs I typically use the bevy inputs directly, since I haven’t really dove into leafwing. Is there a way to access the leafwing inputs client side that won’t interfere with lightyear? Or are you not consuming the events? Cuz one thing that confuses me ergonomically is that if you defined an action that is only used client side I guess that would still be synced
Or can you make a separate client side action state with a separate inputmanagerplugin that is not included in the protocol
Yes you're free to use the leafwing input manager plugin directly for inputs that you don't want to be sent to the server
You can create one plugin per separate action type: https://github.com/Leafwing-Studios/leafwing-input-manager/blob/main/examples/minimal.rs#L9
Update on rust in rust... added modular + rigged characters, syncing the other players' camera forward (just a component added to the player bundle) and rotating the head bone. Also added a terrain blending shader
@pine cape since we seem to have gathered every option we have on the throttling issue, have you decided on a path moving forward?
I'll try to do a prototype where the client sends a message to the server to 'pause' the connection when it detects that the tab is about to get suspended. When it comes back to the tab, it sends a message to 'resume' the connection
nice!!
okay that sounds good i think
looking good
Nice work, seems to be progressing quickly!
@pine cape what exactly is the xpbd feature for? :v
doesnt xpbd already have impls for all necessary traits used for replication
It inplements linear interpolation traits for the xpbd traits
but isnt lerp already implemented by default?
No, the types need to implement the Linear trait
hm alr
If an entity is despawned, what is the expected behavior with children? E.g. I have a player entity on which I insert a bunch of children like meshes on the Interpolated version. But when the player despawns those child meshes are still visible on screen. What's confusing is they don't show in the inspector anymore. Is lightyear supposed to despawn recursive on that entity for you, or is the idea to handle it during the EntityDespawnEvent manually?
I call despawn_recursive on the Confirmed entity when the remote entity is despawned, but I only call despawn on the Interpolated entity; I can change it to despawn_recursive
Added
Thanks, that fixed it
I think there might be a bug with the PartialEq change. Sometimes when an entity is spawned, there is a possibility that the Interpolated entity does not get updated. As you can see here Position is set to all 0's on the Interpolated entity while Confirmed has values.
Component setup:
app.register_component::<Position>(ChannelDirection::ServerToClient);
app.add_prediction::<Position>(ComponentSyncMode::Full);
app.add_interpolation::<Position>(ComponentSyncMode::Full);
app.add_interpolation_fn::<Position>(PositionLinearInterpolation::lerp);
Replication config on the entity:
Replicate {
target: ReplicationTarget {
target: NetworkTarget::All,
},
sync: SyncTarget {
interpolation: NetworkTarget::All,
..default()
},
..default()
},
It's very unpredictable, sometimes when it spawns it updates but other times not. E.g. I can have one player see the right Position and another is stuck at 0, 0, 0
If an update does come through from the server it will update, but that initial setting of the value is acting strange
We don't add the Interpolated component immediately on the interpolated entity
instead we wait for either:
- we have received 2 server updates
- at least 1.3 s *
send_intervalhas passed
Hmm ok, in this case the entity is not being actively controlled, so when it stops moving the xpbd is putting it to sleep so there are no updates happening on it, so by the time a player sees it there are no changes
Do you see this problem only when the entity gets spawned?
Or also afterwards?
So far only at spawn. Like it will spawn at 0, 0, 0 on the screen, if I nudge it that triggers an update since the physics wakes up and then I will get the Position to update on the interpolated entity
I guess this makes sense, if ComponentSyncMode::Full is defined as " Interpolated: we will run interpolation between the last 2 confirmed states"
But if I have a Confirmed position client side on spawn, I would expect that to maybe be the initial value or something
Ok thanks, that shouldn't happen. Normally the logic is:
- spawn Interpolated entity
- if there's 2 server updates, insert the component with interpolation
- if we don't have 2 server updates (because the character was spawned without moving) but 1.3 *
send_intervalhas elapsed, we insert the component with the initial server value received. (to avoid the problem of not having 2 server updates if the character didn't move at all after spawn)
I guess what I don't get is why it spawns at 0,0,0 on the screen. Normally the component is not even added initially on the Interpolated entity
Maybe you're adding the Transform yourself by adding a mesh? Initially the Transform or Position is not inserted on the Interpolated entity
I don't replicate Transform, only Position
I grabbed your debug_interpolate from one of the examples if this helps:
2024-05-19T02:06:08.772254Z INFO app::world: tail_status=InterpolateStatus { start: None, end: None, current_tick: Tick(1253), current_overstep: 0.28259802 } tail_history=ConfirmedHistory { buffer: ReadyBuffer { heap: [] } }
2024-05-19T02:06:08.790999Z INFO app::world: interpolation debug tick=Tick(1261)
2024-05-19T02:06:08.791383Z INFO app::world: tail_status=InterpolateStatus { start: None, end: None, current_tick: Tick(1254), current_overstep: 0.3851324 } tail_history=ConfirmedHistory { buffer: ReadyBuffer { heap: [] } }
2024-05-19T02:06:08.805182Z INFO app::world: interpolation debug tick=Tick(1262)
But are you adding Position yourself on the PredictedEntity in any way?
(Maybe by adding Transform, and then xpbd inserts a Position component?)
Because normally Position would get inserted here: https://github.com/cBournhonesque/lightyear/blob/71308affc88b25d53e4bb1d4d4514a3306890395/lightyear/src/client/interpolation/interpolate.rs#L246-L246
but this system does not run if Position already exists on the entity
Oh, actually I am lol
let me take that out and test
Yeah, I think that was it... I had Position::default(), in there... which makes sense why it was 0, 0,0 as well
Yes the current system is a bit confusing
The wait for 2 server updates didn't work well for moving objects like bullets because the bullet would be frozen in place until we had 2 server updates
But for things like player characters it's ok to just wait for 2 server updates
Is there a way to figure out if I'm the server?
I'm using the host-server mode, and i'd like to have some systems only running if i'm the server/host
https://github.com/cBournhonesque/lightyear/blob/main/examples/lobby/src/server.rs#L28
You can do it like this. There is another method in the same struct, is_host_server_condition.
The best way is probably https://github.com/cBournhonesque/lightyear/blob/main/lightyear%2Fsrc%2Fshared%2Fplugin.rs#L63
It's used here for instance https://github.com/cBournhonesque/lightyear/blob/main/examples%2Fbullet_prespawn%2Fsrc%2Fshared.rs#L226
I decided to create an IsServer resource on the server side and then use that with resource_exists
but a convenience like this could maybe be upstreamed?
also my steamworks account came through so i'll be digging into that again when i have time
Because Identity cannot be used in run_conditions?
yes
I added common run_conditions here: https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/shared/run_conditions.rs
@pine cape according to my tests this should work
so maybe making a custom scheduler plugin could work
(i might not be up to date on the state of this tho, so maybe u already solved that issue haha)
I have an experimental branch: https://github.com/cBournhonesque/lightyear/blob/cb/pause-network/lightyear/src/client/pause.rs#L48
However I cannot a callback to get triggered when the tab gets hidden
do you want to collaborate on that branch?
The idea would be to send a message from client to server when the tab gets hidden
I think the rest would work
alr
oh ur using pagehide?
yea that wont work
how come
Ok i'll try visibilitychange
this should be the way to go i think
the thing is that i don't know how to have access to my bevy resources (ConnectionManager) from within the callback
u can also check for document.hidden btw
as a way to do that in sync
maybe in the callback
like the schedule runner plugin does i think
iirc it takes an rc of the app and passes it to the runner closure
so maybe we can somehow reuse that rc
hm maybe
I tried to listen to the winit event WindowOccluded, but since bevy is paused I only get access to it when I come back to the tab
hmm
yea
so maybe just a custom schedule runner
idt anything else makes sense
because the runner would consume the rc<app> i just saw
so we cant get it anymore
we could do some black magic by illegally cloning the ref to the app since we are in wasm land and everything is single threaded
but im not a big fan of that tbh
Not a fan of a custom schedule runner either; it would be similar to the winit one, except that we run one extra app.update() when the tab gets hidden?
yea
btw thats literally what calling app.update is, we are essentially making a parallel schedule runner
except bevy doesnt support that rn, so we would have to do some tricks to get a ref of the app
maybe the best way is to add this to bevy if other people need it
idt there is a need for that except for networking libs
i wonder if the other networking libs solved that issue
lol whoever made this really didnt want anyone to understand this
where is this from
wouldn't we want a modified version of the winit plugin?
idk if its possible to have two runners in parallel, if yes then there is no need really
so the ScheduleRunnerPlugin ticks every tick_duration even if the app is backgrounded?
general rust principles tell me its not possible
(apart from throttling)
but idk
well yea
but the setTimeout itself would be throttled on the js side
so it doesnt really matter
the problem is this doesn't use requestAnimationFrame anymore
you arent guaranteed that the cb is run at the exact delay*
yea
the question is, can this work alongside winit
or does this replace winits event loop
apparently it does
welp
ok new plan:
- figure out how to extract the runner fn from winit (im thinking since its setting the runner fn we can just intercept it after adding the plugin?)
- modify the scheduler plugin to call winits event loop if existing (if not, just app.update()) on every rAF / settimeout / once, whatever the user needs
- since we have access to the rc<app>, make another closure that runs on pagevisibility
@pine cape sounds good?
- in lightyear's ClientPlugin, we can just call
app.set_runner()to override the runner - create our own version of
winit_runner, which just callsapp.update()once onpagehidden(or whatever the even is). Or just use rc<app> with a closure that sends the message to the server
but what about apps that dont have winit
i think we can just say that apps have to use winit to run in wasm with lightyear
i agree with 1) but why cant we just hijack the winit runner instead of making our own
sounds like way too much work
99% sure already do, no? since it's in the DefaultPlugins
by making our own I just mean copy-paste the code
u can run lightyear without winit atm
thats all the bevy stuff im using eg
doesn't winit provide the input events?
yea, i just add them myself lol
its a bit messy but allows u to do ur own scheduling
well shrug
ig having winit as a req makes sense too
I mean some users use ScheduleRunnerPlugin and some use WinitPlugin
whatever is easier for you
yea since we are on the web u can totally make a headless app and do the rendering in js/dom (eg)
thats what im doing at least
but its possible to just Winit + no renderer
so
shrug
@pine cape also interesting approach
https://github.com/seurimas/bevy_wasm_scripting/blob/main/src/world_pointer.rs
basically what i mentioned earlier but they unsafely duplicate the world ref, not the app itself
seems to work, test gets logged but the entity doesnt actually seem to get spawned
yea that doesnt seem right
i cant believe this works lmao
@pine cape well i guess thats one way to make callbacks which run the main schedule
like this actually works without errors or smth
its technically unsafe due to concurrency issues but since we are in wasm land its not
ig u could prob get rid of the static mut
but im not that good at unsafe rust so i just gone the easy way
this isn't bad! maybe we can integrate this with winit (or with the scheduleRunnerPlugin)
it can just be done with a regular plugin i think
ah nvm
same thing with plugins
so ig only option are systems
prob has to do with the mem::replace in app::run
@pine cape only 1 system
toggle could just be some lightyear resource ig
but yea i tested and this actually runs all systems etc
unless u replace the main label during app creation
looks good or u got any improvements?
@pine cape after some tests i can conclude that at least on chromium based browsers we can run the main schedule consistently at an interval of one second when the tab is inactive
is that good enough?
i havent tested on firefox yet though
and no way for me to test on safari either
firefox is 1 sec too
relatively clean implementation with visibility change listener + set interval
im guessing you could also just have one setinterval that checks for document.visibilityState and if hidden runs the schedule
do you have a branch I could look at? how do we restore the original schedule when the tab becomes unhidden?
oh nah im just prototyping haha
We dont need to restore anything. Its not an actual scheduler after all. The eventloop will still run every frame, however it wont during tab hide, so during that time we set an interval in js that calls app.update every 1 sec
that way we dont actually timeout
but why doesn't this callback also get throttled?
well it does, but it gets throttled to 1 tick per second
i was reading the book section on prediction and rollback about how you get a Confirmed and a Predicted entity on the client. wondering how/if that can play nice with physics (like xpbd), since if you have a second confirmed entity with various physics components, it will interfere with the simulation. is it sufficient to ensure the Confirmed entity doesn't get a collider or rigidbody?
I make liberal use of Or<(With<Interpolated>, With<Predicted>)>)> to add things like physics components. I haven't felt the need to use the Confirmed entity at all so far
are you predicting all physics entities, or just players?
my static level colliders are not predicted, and then i just skip that whole Or<...> incantation. apart from that there isn't a whole lot moving around at the moment, except player movement and bullets which are both predicted
ok, thanks
There isnt really a point in predicting npcs / static objects imo. Just interpolate those and predict players
i'd need to predict other physics objects, so collisions between players and physics objects aren't janky
well i guess bullets too
yeah doesn't matter about static stuff
Yes the Confirmed entity is just there to receive replication updates, and then sync them to the Predicted or Interpolated entity. In practice you would either:
- add physic components on the Predicted entity if you run prediction
- don't add anything and use interpolation
so i should be able to add physics comps to all the Predicted stuff just fine, players and non-player physics entities
for "everything is predicted, with rollback" mode
yes, this is what I do in this example: https://github.com/cBournhonesque/lightyear/tree/main/examples/leafwing_inputs that has 'everything is predicted with rollback + xpbd'
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
This demo might be easier to follow as well, since it doesn't use pre-prediction (spawning an entity in advance on the client initially, to avoid latency). It also uses tnua for fps controls
Hm well the leafwing_input demos is not that smooth when predicting other players. I don't know if that's just because the client doesn't have access to other players' inputs or because of some other bug. I added Correction to smooth out the rollback over a few frames instead of rolling back instantly, but I'm not 100% sure that is working
@pine cape btw should i make a branch for fixing the tab switching stuff or do u want to solve it in another way?
please make a branch! I'll take a look this weekend
are you sending player 1's inputs out to player 2 immediately when they are received by the server, even before applying them? because with reasonable latency and a few ticks of input delay, it's quite likely that you could get the inputs before they're needed
that reduces the number of frames of inputs you have to guess/extrapolate
I'm not! I'm waiting until they are applied on the server, and then I replicate that to all clients.
That's a very good idea, I will create an issue
yeah i'm seeing some jank for the remote player movement with the leafwing demo – which technically could be buttery smooth if the remote player is just holding down the same direction key right - since you just assume the missing inputs are the same as the last known input for remote players. (jank is when you mispredict because they change direction)
yea, if the remote player is holding down their input key I would expect it to be completely smooth
which is the case if you set Correction to 0. (correction_ticks_factor= 0.0)
I guess another problem is that the server has a configured send_interval (usually 100ms); but when re-broadcasting other players inputs we would want to minimize this. (ideally rebroadcast on the same frame that we received the inputs). Maybe there can be a separate setting for sending inputs
yeah i think the server needs to be prepared to send packets every single tick as needed really
for inputs, at least
I have to think about how to do that.
Maybe the best solution is that by default the server can send packets every single tick, and then you can configure the send_interval for every channel or entity. This would allow me to solve https://github.com/cBournhonesque/lightyear/issues/126 as well
Unreal does this with NetUpdateFrequency: https://docs.unrealengine.com/4.27/en-US/InteractiveExperiences/Networking/Actors/ReplicationFlow/ How to reconcile this with the server's server_updat...
gaffer talks about a prioroty accumulator for that.. i would be happy with inputs every tick, and player updates much more frequently than random physics objects
(rocket league sends the ball updates every tick but players less often iirc)
hmm even with correction=0 still some unexpected jank on the leafwing demo. i just edit leafwing_inputs/assets/settings.ron right?
I already have the priority accumulator implemented, but that only kicks in if you limit the bandwidth; but Unreal does both priority accumulation + setting a replication_rate per interval. (unreal flow)
but yeah let me get back to it.
We could:
- just use priority accumulation with a bandwidth (by default input priority is basically infinite)
- remove send_interval and make send_interval configurable per entity/channel
- have send_interval + a custom_send_interval configurable per entity/channel
yes; it's smooth for me when you keep going in one direction
Is it some very tiny jitter? that might be because the position is updated over FixedUpdate, so you need to visually interpolate during PostUpdate (book)
What do you think would be optimal
- notify of throttling and run one last update (ie event listener + some netcode logic)
- run updates continously while in throttled mode as often as the browser lets us (ie setInterval in a webworker)
I'd probably try 2) first, as it seems easier. If it works there's nothing to do
yes i agree. 1) has a higher likelyhood of being future proof but it seems simpler to simply continue updating every sec or so
my hot take on that: entities could have a minimum time since last send before they start automatically accumulating priority (obv things like collision might just set priority to 1.0 immediately). a cooldown period of sorts. boring objects have a longer cooldown set. then each tick, the server should send anything with a priority > 0 that fits in the bw budget. i'm sure there are various ways to do this though.
plus it seems as if webworkers dont get throttled as much so maybe we can even get below 1s
i think it's reasonable for the server to send a packet every tick, just containing all the player inputs tbh, even if there's nothing else worth sending.
@pine cape should we have a delay (like rAF running at the monitors framerate eg) while the window is inactive?
im thinking it might be useful to have this user configurable
but idrk
or maybe imitate raf framerate
but that would be hard without collecting stats about frame time i think
also should we actually let lightyear know its in background mode or just business as usual
cuz it seems like a waste to continue sending inputs while in bg
well we'll probably go with option 1 in the end, but it would be nice to see if option 2 works first
it does
webworkers have no throttling at all
even after 5+ mins
idk for mobile but at least in desktop it works with chromium and firefox
you didn't spawn a webworker though, no?
im just thinking that we should prob have some kind of delay
i did
what about the option without? doesn't webworker kill performance?
not really
there is only like 16 ms delay when switching from active to bg cuz of the latency
I thought we were trying without webworker, with 1 second delay; and check if the keepalives are still sent
yea me too, but turns out after a min the delay becomes 1 minute per timer tick
I think it would be good to document all this, could you open an issue or a PR with what you tried?
yea i made a branch rn
so we can know for exampel that the delay becomes 1 minute without web worker
ill prob link the pr with the existing issue
ok apparently there is no throttling for webworkers on mobile either (at least android / chrome)
i mean it kinda makes sense that thats the case ig
i could put that in notes.md?
maybe
just in the PR description is fine i think
so with webworkers, you would have a resource to toggle?
no
we just check for document.hidden every message
if hidden we run the app.update
its a bit wasteful but still better than making a new worker everytime the tab is in background
plus we can still optimize this a lot by just having a delay in the workers setInterval
thanks! I don't see any commits in your branch though
yea im testing locally
idrk yet where to put this
i mean its just a startup system so maybe in utils
¯_(ツ)_/¯
is it alr to put a random #cfg if in the code somewhere so this only runs in wasm?
yes
@pine cape i think should work now
wow it works really well, amazing
this might even be better than 'pausing' the connection because there's no need to figure out how update the connection timeouts on the fly. + those update calls don't run rendering so they should be fairly cheap; they just keep the local ECS up-to-date with the last 1 second of buffered data
I might want to add a new NetworkingState Background or Paused, where some plugins stop running (Inputs, etc.)
but your webworker runs at 60Hz?
dont' we want 1 Hz?
also is calling clear_trackers() needed? isn't that part of the Last schedule?
@stiff quiver i tested your change with a couple of examples, it seems to work really well, I merged it here! https://github.com/cBournhonesque/lightyear/pull/371
Thanks a lot for fixing this, it's awesome
Maybe you could create a separate crate for this and post in #web or #crates ? I think a lot of people would be interested in this functionality
Hi there! I'm trying to cook up a stupid-simple config for experimenting (no replication, just a Message and Channel) and and I'm running into a never-ending string of lightyear::connection::netcode::client: error updating netcode client: Transport(Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })) on the client. Since you can't add/remove plugins at will I'm starting up the client with the ServerPlugins, might that be causing it?
Hm i don't think i have enough information to help you; do you have some code you could share? You can DM me
Fixed (❤️ periwink), but for future reference;
- client::NetConfig::NetCode needs a Manual configuration specifying the remote IP
- client::IoConfig doesn't refer to the remote host address, keep it on localhost port 0 like the docs tell you to
Thanks for the feedback; I added this simple example: https://github.com/cBournhonesque/lightyear/tree/main/examples/simple_setup
that doesn't use the common test harness, to make it more clear how to create the ClientPlugins and ServerPlugins
Hell yea, awesome :)
Can do that yea :p
Idk i just copied app.update 😂
Yea. But its easily configurable
ah on a side note, i think i mentioned before, but this is unreliable anyways (timers are much more unreliable than rAF), so ideally we would want a bit faster, maybe 2-3 updates per second or so for redundancy
but yea should be handed off to the user ig
i set correction_ticks_factor: 0.0 and conditioner: None for the leafwing demo. added debugging for when rollbacks happen. observed that while player 2 holds down (say) up, player 1 continuously does rollbacks. i would expect there to be a single rollback when player 2 depresses up, after which player 1 just predicts – correctly – that up remains depressed, so no more rollbacks necessary until player 2 releases the up key
ie, i expected a single rollback per unpredictable event, such as a remote player keyboard state changing.
digging in further to the leafwing example - i was expecting to see a history for inputs (PlayerActions) at each tick, for the player cubes, so that during rollback you can use the inputs for the currently-being-resimulated tick in the player_movement system (same for local and remote players). am i right in thinking this isn't happening?
ah that's in InputBuffer within the input_leafwing plugin it seems
however, the remote player appears as two entities, one with Confirmed, which has the replicated ActionState but not collider/rigidbody stuff, and one with Predicted, which doesn't have ActionState, but has all the physics components (ie, the one we render). so the player_movement system can't be moving the Predicted remote player, since it doesn't have ActionState? doesn't it need to be running movement based off historical inputs during rollbacks for resimulation to work properly, is that happening somehow?
adding this system simulates the remote players properly i think - i just get one rollback per key change now:
// take actionstate from remote confirmed players, use it to move the predicted player
#[allow(clippy::type_complexity)]
fn remote_player_movement(
tick_manager: Res<TickManager>,
confirmed_players: Query<(&Confirmed, &ActionState<PlayerActions>)>,
mut predicted_players: Query<
(Entity, &mut LinearVelocity),
(Without<ActionState<PlayerActions>>, With<Predicted>),
>,
) {
for (confirmed, action_state) in confirmed_players.iter() {
if let Ok((e, velocity)) = predicted_players.get_mut(confirmed.predicted.unwrap()) {
if !action_state.get_pressed().is_empty() {
info!(
"Applying movement to remote player {e:?}: {:?} @ {:?}",
action_state.get_pressed(),
tick_manager.tick()
);
shared_movement_behaviour(velocity, action_state);
}
}
}
}
Thanks, this sounds like a bug introduced by the protocol refactor; I think the Predicted entities for the remote players should have ActionState (and maybe History<ActionState>?)
yes that was it.. the ActionState component used to be synced from the Confirmed to the Predicted entity: https://github.com/cBournhonesque/lightyear/blob/0.13.0/macros/src/component.rs#L138
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
I pushed a fix: https://github.com/cBournhonesque/lightyear/pull/374
Thanks so much for looking into this, I definitely need more tests around multi-player prediction. I think I'm going to handle this differently though; as you said I don't wait until the ActionState component is replicated, instead I might want to forward the LeafwingInputMessage immediately as you said (which contains inputs a bit ahead of time compare to ActionState, since ActionState waits until the inputs are applied
Prediction for other players was broken because we weren't copying the ActionState component to other players Predicted entity, like we used to: https://github.com/cBournhonesque/lightyear/blob...
cool. yes that did the trick. removed my system and still just seeing 1 rollback per input change now
but yeah agreed, need to send out inputs from the server immediately, which should reduce mispredictions in various setups
One issue is that the LeafwingInputMessage only contains diffs as an optimization, but for other players we wouldn't have a base state to start with. Maybe I can replicate other players ActionState once every few seconds, and apart from that rely on the input message.
also what's nice about your solution is that it doesn't seem to call any render system?
Since app.update() is not called, we don't run the rendering sub_app
Oh yea :o
Ah but if users have sub apps that kinda sucks no?
i think that's fine; users don't have subapps 🙂
what
why not
Cant they make subapps too
yeah but no one does, we can worry about that when someone runs into an issue
Actually I'm not sure if that's even possible.
One of my invariants is that all the components for a replicated entity are on the same tick, the "confirmed" tick.
I do that so that when I apply rollback I know that the entity state is exactly what was on the server on the confirmed tick, and the rollback can work properly.
But with this change we might get input updates (i.e. ActionState updates) more frequently, so the tick of the ActionState might be different from the "confirmed" tick of the replicated entity.
Or is the goal not to have more frequent rollbacks based on inputs; but to have access to more of the remote players' inputs when we do rollbacks?
it makes sense that components for an entity are all on the same tick. but having the remote players' inputs as soon as possible means you can can use them during simulating frames that are ahead of the server, which will include various rollback scenarios. it will probably eliminate some rollbacks altogether, or at least reduce their size. since a big source of mispredictions will be when remote users change which keys they are pressing.
somewhere i have code like this:
// per fixedupdate frame not during rollback:
let inputs = get_keys_pressed();
let tick = tick_manager.tick();
store_inputs_for_tick(tick + input_delay_ticks, inputs);
and then more code which gets the stored inputs for the tick_manager.tick() and writes to the player's InputsThisFrame component (or something like that)
not sure how that fits with the current idea of replicating all components. maybe the buffer of unapplied inputs could be a component which is replicated every tick, and a system that applies from the buffer as needed
havin access to more of the remote players' inputs when we do rollbacks leads to fewer rollbacks overall
makes sense, i'll try to implement that today
Should Confirmed entities be automatically despawned if their Interpreted or Predicted counterparts get despawned or is that something we need to handle?
Seem to have some sticking around, but haven't dug in too deep yet
I don't think so, I think the Confirmed entity is only despawned when the entity is despawned on the server. The Confirmed entity is purely there to receive the replication updates
oh right that makes sense, let me check if they're getting stuck on the server
i suggest to reuse the last known input if no input is available for a remote player at the current tick. ie, assume if they were holding up, they are still holding it. keeping that guesswork simple like that worked best for me. i tried a few other more complicated heuristics but just copying the last input worked best. i'm not sure if that's happening at the mo.
yep I'm already doing that
even past the last frame received from the server? ie into the future
yea, i keep using the last ActionState received
cool
I think the current system works well, we just might be losing a few ticks from the server that we might have if we send the Inputs directly instead of the ActionState.
Currently: we rollback at tick 12 because we received a server update that shows a mismatch; we have the ActionState at tick 12. During rollback we use the ActionState from tick 12 (i.e. we consider that the remote player keeps pressing a key)
Planned: we rollback at tick 12 because we received a server update that shows a mismatch; we have received an InputMessage that contain InputDiffs from the remote player up to tick 14, which allows us to reconstruct the remote player's ActionState up to tick 14. We do the rollback, using the remote player's ActionState from tick 12, 13, 14 (and then we keep the ActionState of tick 14 for the rest of the rollback)
The one thing I'm worried about is that the server is sending remote player's InputDiffs instead of the complete ActionState. This probably breaks if the remote player entity gets replicated while the other player aws pressing some keys, because applying the diffs won't give you the correct InputState.
I can just default to sending the full ActionState for now.
I guess this is a bigger problem: how to do delta-compression correctly by sending a base state and then diffs, and how to recover if something goes wrong.
sounds good. how important is diffs for the input state? 1 byte is ample per input in the demo, up/down/left/right.
could just send the full input for now. altho more complicated games might have larger inputs.
I'm using leafwing input manager, where the inputs (ActionState) have more data (durations, etc.)
But yes doesnt matter too much for now
oh yes true, much more than a bit per key
if you have to send 4 inputs, ticks 12,13,14,15, maybe send full for 12 then 3 diffs
does channel prioritization happen on a per-connection basis or is it across the board? like if you use a channel for game messages and had another separate one for like voice chat or something, and you always want game messages to be higher priority, if like 100 people all used voice chat at the same time, would that bottleneck the game messages?
The bandwidth cap and the prioritization are both per connection!
@wintry dome I merged the change for remote player prediction: https://github.com/cBournhonesque/lightyear/pull/376
I wasn't able to have a fully clean implementation but it does seem to help. I've checked in the logs that we are using the raw remote player input messages during rollback to have more precision during rollback.
nice, remote player movement feels a bit smoother now i think 👍 would need to be measuring the number of rollbacks and graphing rollback window sizes to know exactly
@pine cape idk if thats already the case (didnt seem so) but wouldnt it be more reasonable to only replicate player inputs to the client for players that are actually replicated to the client?
maybe i missed it. But it seemed like u are sending inputs from every single connected player
That's a good idea; i'e tried using tracing-metrics to compute some metrics but it seems a but unwieldy, maybe i'll can use diagnostics for that instead
It's controlled by the user, they have to decide to rebroadcast inputs: https://github.com/cBournhonesque/lightyear/blob/main/examples/xpbd_physics/src/server.rs#L114
this is awesome!
how about something like this, writing to a metrics resource, then flushing to diagnostics periodically
i wrote something similar for replicon too
i extended the existing client diagnostics plugin. i could imagine adding lots more diagnostics, since there's plenty you'd want to display during dev/debugging. separate requirements from exporting to prometheus
Yes prometheus/grafana doesn't have any in-game overlay, which you need for debugging
is it ok if i make a new branch from your changes? I want to change some names, etc.
Actually i'll merge your change first
yes go ahead
nice stuff! i would love to be able to have graphs of bandwidth consumption over time, and a list of what's taking up that bandwidth. maybe i'll get to that at some point
There must a plugin somewhere that computes graphs over time from diagnostics data
https://github.com/sagan-software/bevy_diagnostic_visualizer it's a bit old though
there were talks of upstreaming https://github.com/IyesGames/iyes_perf_ui a while ago but it seems that the library has various issues atm
(and it would have customizable little plotting displays)
yeah egui is a solid choice for this
actually i'm not sure that writing to an intermediate resource and flushing to diagnostics periodically is more performant than just writing to Diagnostics directly.
Diagnostics uses Deferred so we can write to Diagnostics in parallel
Or is the problem that if we just send a rollback measurement to diagnostics, we wouldn't be able to get the total count? since the total would just be computed over the history?
yeah you're at he mercy of diagnostics, can't easily calculate eg rollbacks per second if you write directly I think
hence the resource and system to do any custom things before writing diags
and access to history too, for graphs. that could be in the metrics resource in future
I treat diags like an executive summary 🤷♂️
Another problem is that diagnostics doesn't have good support for 'namespaces'
ideally i would want one diagnostics per connection on the server
yes, true. I think a custom metrics resource will be increasingly useful
I know this wouldn't be following the Netcode standard. But do you think it would be worth adding a status flag to DeniedPacket and sending back the specific reason? Right now, the client only ever just gets a generic connection error. But it seems strange to have "denied" send only when it's full. One example is if you forget to change your client id, or if the same client id is logged in. The context only really exists in these debug statements. Also a related "nice to have" would be some way to make pluggable validations like to be able to add your own condition of whether a client should be denied for bans or whatever
both suggestions make sense to me! creating tickets for them
Possible this is for ddos prevention
less bandwidth waste rejecting invalid cons
I feel like the value you get from a UX standpoint is a better tradeoff than worrying about sending 1 byte. I could see maybe with raw UDP but pretty sure webtransport or anything else really, is either going to mitigate that (e.g. in the web transport spec data sent by client before the server permits it is invalid behavior) or make it worse (as in the ddos bottleneck will happen in the transport layer, like flooding a websocket which has head of line blocking)
Yea totally, just saying that might be the reason
@dull lion is there already a way for me to access the steam client as a resource?
on my branch, or on lightyear main?
on your branch :)
on my branch you can create the steam client yourself and stick it in a resource
i see
that worked, thanks :)
That definitely seems better
@pine cape is there a way for me to check if the ClientId i get is myself?
basically, i'm looping through all connected players and if it's myself i'd like to do something specific
i thought maybe is_local would do that, but that only checks if the client id is of type Local
my current solution is to add a resource on a on_connect event and store the given ID. then i just compare the two
this works, just wondering if there's any other solution
For host-server mode?
yes
maybe connection: Res<ClientConnection> and connection.id()
yep that works :)
What should I consider when using an ActionStateDriver to drive an action? I've got a LeafwingInputPlugin with a DualAxis action that's being set like so from a system:
action_data.axis_pair = Some(DualAxisData::from_xy(dir));
action_state.set_changed();
action_state.press(&PlayerActions::Look);
Server->client replication works just fine, but not the other way round. Can supply more code if needed
If I insert eg. VirtualDPad::arrowkeys into the initial InputMap then it works fine using the arrow keys both-ways
You need to add an InputMap on your entity for the inputs to be sent from client to server
info!("Spawning {:?}", event.client_id());
let map = InputMap::default().insert_multiple([
(PlayerActions::Up, KeyCode::KeyW),
(PlayerActions::Down, KeyCode::KeyS),
(PlayerActions::Left, KeyCode::KeyA),
(PlayerActions::Right, KeyCode::KeyD),
]).insert(PlayerActions::Look, VirtualDPad::arrow_keys()).to_owned();
let player = commands.spawn(
PlayerBundle::new(
event.client_id(),
InputManagerBundle::<PlayerActions>::with_map(map)
)).id();
commands.entity(window.single()).insert(ActionStateDriver {
action: PlayerActions::Look,
targets: player.into()
});
Now that I think about it i haven't tried both using the driver and the VirtualDPad::arrow_keys at the same time so ill give that a go
If it's an issue with the Look action not being in the inputmap then I wonder if there's a placeholder that allows me to control it via a driver without say arrow_keys()
I just require an InputMap to track which entity is 'controlled' by the player and add some extra internal input-handling components. But beyond that the only thing that matters is changes to the ActionState, so you're free to update the ActionState however you want (via inputs, ActionDriver, updating ActionState manually, etc.)
How do you go about messing with the action data? Been messing around with how I update/insert it still isn't replicating client->server
if let Some(data) = action_data {
data.axis_pair = Some(DualAxisData::from_xy(dir));
action_state.press(&PlayerActions::Look);
}
else {
action_state.set_action_data(PlayerActions::Look,
ActionData {
state: ButtonState::Pressed,
axis_pair: Some(DualAxisData::from_xy(dir)),
..Default::default()
}
);
}
This is a client and a dedicated server, right?
I'm running a HostServer if that's what you mean
and the 'client' you were mentioning is the 'host-client'?
The instance running the server + local client would replicate fine to the just netcode client, but not the other way round
In which schedule are you updating the ActionState?
Before I followed this using action_state.action_data_mut and that wouldn't replicate the Look actions presence at all, but after changing to the above the Look action data would show, but none of the changes would replicate to the client. I've tried both what's shown here in the InputManagerSystem::ManualControl as well as in the FIxedSet
Yes I would suggest updating the action_state directly in the ManualControl set, or
In the FixedPreUpdate schedule before the BufferClientInputs set ( https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/client/input/leafwing.rs#L311)
Maybe also confirm via the inspector that the ActionDiffBuffer and InputBuffer components are added?
Or do you have a github I could look at? I would be easier if I could experiment. I've only tried ActionState updated via InputMap
Ill set that up and dm, one min
Fixed: the solution was that the ActionState has to be updated in
PreUpdateSchedule in theManualControlsystemset set
(we generate the input diffs in PreUpdate after all the leafwing system sets have run)
@dull lion so i just had my first attempt with your p2p branch, and it failed to connect.
the host received a connection request, but then nothing happened.
I'll look more into it tomorrow (maybe today already) and will look into what the problem is
are you testing with two steam accounts? i realized you need two machines and two steam accounts to properly test it and then i got lazy 😅
yep, two machines two different accounts :)
i already once implemented steam p2p into my own project, but it got super annoying the need to have a second machine all the time, which is why i decided to switch to lightyear
right. yeah it's convenient to have the option to not use steam networking during development
anyway, looking forward to hearing about your progress! i'll have to get this sorted in the next month or so, but i've gotten sidetracked on some other stuff now
yea no worries! I currently got some time, as i'm done with most of my final stuff
if i find a fix can i just create a merge request on your branch then?
please do!
great
if i wanted to change the xpbd demo so the player cube is spawned by the server, is it correct for the server to spawn it with SyncTarget{ prediction: NetworkTarget::All }, and an ActionState::<PlayerActions> then when the client notices it (and playerid matches our own), add the InputMap to the predicted entity?
Yes I think that works; you can also use ControlledBy on the server-side to indicate which clients are controlling the entity. Then the entity on the client will have a Controlled component you can filter with (instead of manually comparing playerids)
Component storing metadata about which clients have control over the entity
Marker component to indicate that the entity is under the control of the local peer
thanks, will give that a try
ah yes i'm already adding ControlledBy, since it's in the Replicate bundle. i should check for that on the client
yes I should update the examples to use Controlled on the receiver side
ah it's the Confirmed entity on the client that gets the Controlled component though
2 queries needed. nice that Controlled gets added automatically though, better than comparing ids
yes; maybe I should sync Controlled automatically to the Predicted/Interpolated entities, since they're also controlled by the same player
