#lightyear
1 messages Β· Page 2 of 1
@pine cape
good hour.
how properly remove my player gltf from the screen of all other connected clients, after my player client was exit using window X button ?
probably it is Received entity despawn: 13v3 stuff
i will be enough just move it outside the map for my case, but i would like to know where and how should i manage this situation to do it more soft way
probably i should try to get and change the player position somewhere here ?
/// Server connection system, create a player upon connection
pub(crate) fn handle_connections(
mut connections: EventReader<ConnectEvent>,
mut disconnections: EventReader<DisconnectEvent>,
mut global: ResMut<Global>,
mut commands: Commands,
) {
for connection in connections.read() {
let client_id = connection.context();
// Generate pseudo random color from client id.
let h = (((client_id * 30) % 360) as f32) / 360.0;
let entity = commands.spawn(PlayerBundle::new(
*client_id,
get_random_spawn_position(),
Color::hsl(h, 0.8, 0.5),
));
// Add a mapping from client id to entity id
global
.client_id_to_entity_id
.insert(*client_id, entity.id());
}
for disconnection in disconnections.read() {
let client_id = disconnection.context();
if let Some(entity) = global.client_id_to_entity_id.remove(client_id) {
// HOW TO get the position of the entity here to change it to a dead player?
commands.entity(entity).despawn();
}
}
}
@floral vector you gotta add a query inside the params
maybe something like Query<&Position>
and then you can see if that query contains that "entity"
and you can get the position component
i can try to implement this like in other methods, but it looks like... huge injection inside code, and method became not so accuracy. is it ok?
you can do it somewhere else if you want
you can make another system
.add_systems(Update, handle_disconnected_player_cleanup)
just want to
- implement this
- not make code super ugly(but this is non-mandatory)
handle_disconnected_player_cleanup can have the:
mut disconnections: EventReader<DisconnectEvent>,
mut global: ResMut<Global>,
mut commands: Commands,
positions: Query<&Positions>
@floral vector wait
the id would have been removed from the Global so you cant make this into another system simply
this new system would also have to run before the "handle_connections"
I'd just add the despawn logic with position in "handle_connections"
oh, i see
Or just throw another event to cleanup that entity
then should add the method but not system
or add a component to that entity to indicate that it will be cleaned up (not a replicated component)
now i think about function, which will be call before id removed. Other looks complex
What doesn't work with the current logic?
Is the entity not despawned for other clients?
cococore wants to do some special things with the entity before despawning as I understand
and its not related to lightyear at this point at all
What is the "player gltf"? Is it a component attached to the entity? or some kind of child entity? If it's the latter you can try despawn_recursive
the gltf model which i add on screen and manipulate, does not disappear after client disconnected
ah
so you can f.e. connect, move somewhere, disconnect(close the window)... and second client has the ... ghost/garbage
hmm I wonder if lightyear will delete children on client if parent is destroyed on server
pub(crate) fn handle_player_spawn(
mut commands: Commands,
plugin: Res<MyClientPlugin>,
mut player_spawn: EventReader<ComponentInsertEvent<PlayerId>>,
players: Query<&PlayerId>,
asset_server: ResMut<AssetServer>,
) {
for event in player_spawn.read() {
let entity = event.entity();
if let Ok(player_id) = players.get(entity) {
let gltf = SceneBundle {
scene: asset_server.load("player.gltf#Scene0"),
..default()
};
if player_id.0 == plugin.client_id {
// this is the controlled player MINIMAP
// commands.entity(*entity).insert(gltf);
} else {
// this is another player GLOBAL
commands.entity(entity)
.insert(gltf);
}
}
}
}
I don't think so; I should change all the despawn calls to despawn_recursive so that children are also deleted
But but other clients wouldn't automatically handle the despawned players character no?
Unless there is something we have to sync the objects in hierarchy
so a child objects destruction is replicated
I think I'd avoid hierarchies in multiplayer
I mean I'd use hierarchies but I'd manage it in update loop
I'm thinking of doing this: https://github.com/cBournhonesque/lightyear/issues/52
i.e. just copy the Replicate component from a parent to children
so that when you delete the parent + children via despawn_recursive, the children's deletion will also be replicated
it's also a pretty minor change, just need to add one system that propagates the Replicate component to a hierarchy + makes sure that they use the same ReplicationGroup (the ReplicationGroup of the parent entity)
In this case for example, I'd have a system like this:
fn sync_character_view(
models,
character_query: Query of networked character objects
character_view_query: Query of view objects
)
{
//delete orphaned views
//create views for characters without view
}
hm
The gltf model is also being added manually here I think
If its addition is manual, maybe its right if its disposal is manual as well
what if your player created the original helmet from clay, and then wear it in the battle. After the player was defeated you should manage the helmet separately?
probably i miss something
I think helmet work on the player should not be the same as the helmet as an "item on the ground"
helmet on the player should just be a rust enum on the CharacterComponent:
CharacterComponent:
worn_helmet: Option<HelmetType>
item on the ground:
GroundItem:
data : ItemType
enum ItemType:
Armor:
HelmetType(helmet_id, hitpoints)
TorsoType(torso_id, hitpoints)
MeleeWeapon:
Sword,
etc etc
and there would be some other system for syncing the views
for both the character and the ground item
@floral vector
so we keep the replicated and logical part simple
@pine cape
should i
1 - wait/expect of addition of some recursive implementation for disconnect event,
or
2 - try to implement injection to move disconnected player entity outside the playground(the easiest way i can imagine) ?
I don't understand why the current code doesn't work right now.
If GLTF is a component attached to the entity, then when the entity is despawned for other clients, the GLTF model should be destroyed no?
can not say in exact, can only record video π when the gltf still inside other client world but despawend event already happened
it was before too, the solution was just move the gltf outside the game level.... x = 25.0 , after that, when you die(moved outside too) , you can see previous players ghosts
I cna't say without more information. You should run with bevy_inspector_egui and check which entities/components are still present
Do you mean, generally there is expectation that the gltf will be removed after disconnect event happens ?@pine cape
If you're adding the gltf as components to the entity, yes
like this?
commands.entity(entity).insert(gltf);
then it is not work
why this does not work?
/// Server connection system, create a player upon connection
pub(crate) fn handle_connections(
mut connections: EventReader<ConnectEvent>,
mut disconnections: EventReader<DisconnectEvent>,
mut global: ResMut<Global>,
mut commands: Commands,
mut position_query: Query<&mut PlayerPosition>,
) {
for connection in connections.read() {
let client_id = connection.context();
// Generate pseudo random color from client id.
let h = (((client_id * 30) % 360) as f32) / 360.0;
let entity = commands.spawn(PlayerBundle::new(
*client_id,
get_random_spawn_position(),
Color::hsl(h, 0.8, 0.5),
));
// Add a mapping from client id to entity id
global
.client_id_to_entity_id
.insert(*client_id, entity.id());
}
for disconnection in disconnections.read() {
let client_id = disconnection.context();
// WHY THIS DOES NOT WORK? it is server.rs file
if let Some(player_entity) = global.client_id_to_entity_id.get(client_id) {
if let Ok(mut position) = position_query.get_mut(*player_entity) {
position.x = 25.0; // move player outside of the map
}
}
if let Some(entity) = global.client_id_to_entity_id.remove(client_id) {
commands.entity(entity).despawn();
}
}
}
the player gltf is still on place where it was before client window was closed
hmm @floral vector what if you dont despawn it?
i think it just gets despawned before it can be replicated with x=25 position
i am still stuck. probably i need extend this some way, in client.rs
pub(crate) fn receive_entity_despawn(mut reader: EventReader<EntityDespawnEvent>) {
for event in reader.read() {
info!("Received entity despawn: {:?}", event.entity());
}
}
you tried not despawning anything @floral vector ?
comment out this:
commands.entity(entity).despawn();
nope it does not affect anything
i already separated code into system, and even used preupdate schedule
@elder dome ok, without despawning i can not reconnect again later, it kicks out the client
but entity disappearing... in some reasons my system does not work, so i rollback code to injection in connection handler
I would advise to use the inspector to see if the entity/component got despawned correctly
@pine cape it was not despawned, because i comment the block for this
/// Server connection system, create a player upon connection
pub(crate) fn handle_connections(
mut connections: EventReader<ConnectEvent>,
mut disconnections: EventReader<DisconnectEvent>,
mut global: ResMut<Global>,
mut commands: Commands,
mut position_query: Query<&mut PlayerPosition>,
) {
for connection in connections.read() {
let client_id = connection.context();
// Generate pseudo random color from client id.
let h = (((client_id * 30) % 360) as f32) / 360.0;
let entity = commands.spawn(PlayerBundle::new(
*client_id,
get_random_spawn_position(),
Color::hsl(h, 0.8, 0.5),
));
// Add a mapping from client id to entity id
global
.client_id_to_entity_id
.insert(*client_id, entity.id());
}
for disconnection in disconnections.read() {
let client_id = disconnection.context();
if let Some(player_entity) = global.client_id_to_entity_id.get(client_id) {
if let Ok(mut position) = position_query.get_mut(*player_entity) {
println!("BANG we are here, now move outside");
position.x = 25.0; // move player outside of the map
}
}
// if let Some(entity) = global.client_id_to_entity_id.remove(client_id) {
// commands.entity(entity).despawn();
// }
}
}
i think because you dont remove from global.client_id_to_entity_id
if the client comes with the same id, it might not be able to insert to hash map i think
you checked your error?
i still don't get why
if let Some(entity) = global.client_id_to_entity_id.remove(client_id) {
commands.entity(entity).despawn();
}
doesn't delete the entity including any gltf component, lol.
This should be enough
now. with code above the gltf disappearing, but if i uncomment despawning then it is not works.
ok i will try again , with despawning
no. if i uncomment despawn() method code. The gltf is not disappearing from the client. even after 30 seconds
on closed client i have
2024-01-18T19:40:32.306738Z INFO bevy_window::system: No windows are open, exiting
2024-01-18T19:40:32.308436Z INFO bevy_winit::system: Closing window 0v0
on client i have
2024-01-18T19:40:42.794606Z INFO hybrid::client: Received entity despawn: 13v3
on server i have
2024-01-18T19:40:42.542973Z INFO lightyear::server::connection: Client 63502 disconnected
BANG we are here, now move outside
@pine cape
can you please explain what the gtlf is... (i.e. is it a component? ) or send a screenshot with the inspector open
gltf was created using blender, and i add the result into app using this code
pub(crate) fn handle_player_spawn(
mut commands: Commands,
plugin: Res<MyClientPlugin>,
mut player_spawn: EventReader<ComponentInsertEvent<PlayerId>>,
players: Query<&PlayerId>,
asset_server: ResMut<AssetServer>,
) {
for event in player_spawn.read() {
let entity = event.entity();
if let Ok(player_id) = players.get(entity) {
let gltf = SceneBundle {
scene: asset_server.load("player.gltf#Scene0"),
..default()
};
if player_id.0 == plugin.client_id {
// this is the controlled player MINIMAP
// commands.entity(*entity).insert(gltf);
} else {
// this is another player GLOBAL
commands.entity(entity)
.insert(gltf);
}
}
}
}
inside client.rs -> handle_player_spawn
can you link your repo
i think it would have some hierarchy
A component bundle for a Scene root.
The scene from scene will be spawn as a child of the entity with this component. Once itβs spawned, the entity will have a SceneInstance component.
you should use the inspector
here i explained how to get it, its simple
looks like it's a special component that spawns children
In which case this is not supported right now
I need to add a system to copy Replicate to all child entities
Hmm you sure thats a good idea?
What if user doesn't want to replicate child entities?
I think networking solutions that i've seen handle this via spawning "prefabs"
what is a prefab
You often have a prefab id when you spawn something
An reusable object you use in your game
for example a character prefab
it could come with a deep hierarchy of entities
in these networking solutions, you dont do anything to deal with children
ya you need to despawn_recursive gltf scenes
and you dont replicate children
(for example I mean)
and when server spawns one of these
message is something like this:
prefab id,
replicated components datas
entity id
when clients receives this,
client spawns the prefab
prefab already comes with all these components
and then applies the component datas
yeah i see. There's a subset of a components that are required for replication
but then the clients should spawn a bunch of related component/entities that are render only
Audio/Visuals/Etc., which make up the prefab
and indeed we don't want to replicate them
in lightyear, they need to spawn these visual components
but if there was a prefab workflow like this
they wouldn't have to manually add the components that are not Replicated
but lightyear's case may not be a disadvantage
you allow replication of component addition and destructions
Well it seems like it's also possible right now; since the Gtlf is kind of like a prefab. It spawns a whole hierarchy
I don't think unity netcode for entities supports that kind of thing
and I also dont think other object oriented frameworks i've seen for unity can replicate component addition and deletion
Ok so 2 options:
- when I receive a replicated 'despawn' , I use
despawn_recursiveinstead ofdespawn. (maybe make it configurable? not sure. @hushed rivet there might be cases where this is unwanted) - @floral vector when the entity gets deleted, you need to find the scene that corresponded to that entity and delete it
can not add inspector to client
Client "u1" with id "63502" is trying to call 127.0.0.1:8000
thread 'main' panicked at /home/user/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy-inspector-egui-0.21.0/src/quick.rs:67:17:
Error adding plugin bevy_egui::EguiPlugin: : plugin was already added in application
note: run with RUST_BACKTRACE=1 environment variable to display a backtrace
hmm, actually i think despawn_recursive is fine
when client receives a despawn
hmm so you already have another egui in your app
yes
then its more difficult
IF the entity has children you MUST despawn them or otherwise dissassociate them from the parent(removal of parent component in all children), simply despawning the parent will break the hierarchy
@floral vector just run with --inspector, I already include the inspector
the same error. i have egui fps widget
i can imagine something like this
despawn_recursive(
mode
)
-
default ... everything recursively
-
range ... from level1 up to level2 of nesting.. not sure it is possible
-
list of levels of nesting [ 1 , 6 , 9] with silent skip if no nesting of that level
-
list of ids [ "one", "two", "four"] ... if it is implementable
I have a commit that does despawn_recursive, you can try that for now: https://github.com/cBournhonesque/lightyear/pull/96
will try now.
Tried this, no errors , but the result the same, gltf on screen
for disconnection in disconnections.read() {
let client_id = disconnection.context();
if let Some(entity) = global.client_id_to_entity_id.remove(client_id) {
for ent in entity.entities() {
commands.entity(ent).despawn();
}
commands.entity(entity).despawn();
}
}
i see errors of linter... something not safe
The default runtime flavor is multi_thread, but the rt-multi-thread feature is disabled.rustcClick for full compiler diagnostic
tokio_macros
proc_macro main_rt
Marks async function to be executed by selected runtime. This macro helps set up a Runtime without requiring the user to use Runtime or Builder directly.
Function arguments:
it is error of fn main... where tokio used
Also webtransport and sertificate unavailable in server.rs
no variant named WebTransportClient found for enum lightyear::prelude::TransportConfig
variant not found in lightyear::prelude::TransportConfig
Does it correct syntax?
lightyear = { git = "https://github.com/cBournhonesque/lightyear.git", rev = "0206f5f" }
forgot futures... tired
@pine cape
looks like it works. little bit longer than i expected ... close to 6-8 seconds, than gltf disappeared
Also i can reconnect with the same id
for x3 clients works too. the time of disappearing looks like +-0.3 sec
Didn't dig deep but I noticed the client seems to send the server the client id, unless I missed something, but the server should be sending the client an id instead if this is indeed what is happening
might have glossed over something tho
not sure i understand correct, but i tested using , close the window of the client using mouse click on X button, then wait while other clients will be affected by this
looks like there is non-zero chance we will see 0.7.1 soon π΅
So is the gtlf despawned instantly or not? it should be almost instant (like 0.3 sec max)
@pine cape gltf disappeared 6-8 seconds later after window closed using mouse. Probably this happens not instantly, after close window, because this happens when the server send the despawn or disconnect event to clients. Not sure
0.3 seconds is difference between two clients . we have c1 c2 c3 , c1 closed, c2 and c3 gltf disappeared with difference 0.3 seconds between them. But before disappearing about 8 seconds the gltf still on screen
the reason can be next: the X button of the window does not send any close event to lightyear. And lightyear detects the disconnection relatively, few seconds later
ah yeah lightyear takes a while to register the disconnect. (the timeout for disconnections is configurable)
Then let it be like it is. i am fine with present behavior.
I have side question. When i use toml { git = ".... lightyear.git", rev = "654654" } , will it works after pull request will be merged, and branch deleted from the repo?
If i remember correct it will works. But is there some tiny potential chance that commit can be overriden etc using force flag? But i am pretty sure noone plan to do this specially
I don't think anyone will modify this commit
@elder dome I think I got pre-spawning working; rollback is also performed on the pre-spawned entities now. It also works fine with multiple entities spawning on the same tick, as you said
I sometimes see some slight jitter, probably a problem with the rollback? but otherwise it mostly works
btw I had jitters I couldn't explain in my own spawn prediction experiment
it was happening when I put the projectile and the player in the same replication group
it looked like as if the player entity was rolled back but not resimutaled for a ticks duration
or a frames duration
it was fine if I put them in different ReplicationGroup's
or just kept it default (entity replication group i mean)
hm i'm not sure; i have the player + its bullets in the same replication group
It's probably because I didn't have rollback for prespawned entities before
so the player's rollback (whenever a Confirmed update arrives) would affect the bullets
no i have it working now
yeah it makes sense to do it way of course
but I think you may not see the jitter when you* separate the replication groups
and maybe that will give you a clue, if my assumption is true
I've merged the branch; so you should be able to use prespawning with the latest commit! @elder dome
@pine cape
can you pin the git repository link of lightyear, or crates.io link? To fast access, if it is not break any rules
that users could open them like 1 2 ... or 1 ... but not like... typing in search and then navigating
Github: https://github.com/cBournhonesque/lightyear Book: https://cbournhonesque.github.io/lightyear/book/introduction.html
Pinned!
I just converted my code to use the new PreSpawnedPlayerObject, its looking pretty good
pub fn cause_mis_predictions(mut transforms: Query<&mut Transform>, mut counter: Local<i32>) {
for mut transform in &mut transforms.iter_mut() {
transform.translation += 0.01 * ((*counter % 10) as f32 - 4.5);
}
*counter += 1;
}
Also used this on client to see if it it does anything weird in rollbacks, it also looks fine with this on
little rounds is gravitation?
i was just visually debugging the confirmed and predicted one
i'll be trying to build something interesting after this, now I have everything figured out I think
Great to hear that prespawning works well for you!
The misprediction system is pretty smart, you only apply it on the predicted entity?
like this π
https://youtu.be/JFRHss8ELB4?si=m3x9e-qO0km--RBe&t=127
Riddick (Vin Diesel) fights the Lord Marshal of the Necromongers and becomes their new leader.
What is The Chronicles of Riddick (2004) about?
After years of outrunning ruthless bounty hunters, escaped convict Riddick (Vin Diesel) suddenly finds himself caught between opposing forces in a fight for the future of the human race. Now, waging in...
it was matching the player character and the projectile
Hi @pine cape. I have been working on a hobby game for a few weeks and started to develop a WebTransport-based networking solution. I got to the point of having the server and client connect and communicate, but started searching around for inspiration on efficient ECS synchronisation when I came across your Lightyear crate (I had previously searched for bevy_* crates). I just want to say a huge thanks for this library. It works fantastically well and is teaching me a huge amount about not just what I came looking for but loads of other Rust techniques. I've just finished reworking my codebase to use Lightyear with WebTransport and everything is working great π Thanks!
Awesome, super happy to hear that! Let me know if you need any help π
Thanks, I certainly will do π I'm still getting my head around all the new paradigms. The bitcode serialisation and netcode protocol are all new to me.
yes i need to update a bunch of pages in the book as well: https://cbournhonesque.github.io/lightyear/book/concepts/title.html
Probably it should be bold text about do not add the egui + lightyear separately, otherwise inspector will fail, because of double import of parts of the egui will be detected, to prevent later brainfuck for users
Because of this difficult i could not discover my 3d game world using inspector properly like bargos showed on screenshots
in lightyear/src/client/prediction/despawn.rs PredictionCommandsExt, needs to drop extra lifetimes on EntityCommands just an FYI, noticed while updating to bevy:main recently
can you elaborate? what should I change?
impl PredictionCommandsExt for EntityCommands<'_, '_, '_> { -> impl PredictionCommandsExt for EntityCommands<'_> {
Just merged today! I'm very pleased about that change though
any noise reduction in bevy_ecs is well wanted =p @kindred garden
@sleek crane deserves the credit here :p
Ah I see; I guess I'll only make that change once bevy-0.13.0 comes out
@pine cape i have some potential issue when tried to connect between two macos machines in local network
Client "u1" with id "63502" is trying to call 10.5.126.141:8000 2024-01-23T15:09:28.268321Z INFO bevy_winit::system: Creating new window "Thank you for your help! I want to complete it before February" (0v0) 2024-01-23T15:09:28.299747Z INFO lightyear::netcode::client: client connecting to server 10.5.126.141:8000 [1/1] 2024-01-23T15:09:28.299818Z INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "MacOS 14.3 ", kernel: "23.3.0", cpu: "Apple M1", core_count: "8", memory: "16.0 GiB" } thread 'main' panicked at /Users/sergei.ivanov/.cargo/registry/src/index.crates.io-6f17d22bba15001f/lightyear-0.7.0/src/client/systems.rs:48:46: calledResult::unwrap()on anErrvalue: Io(Os { code: 65, kind: HostUnreachable, message: "No route to host" }) note: run withRUST_BACKTRACE=1environment variable to display a backtrace Encountered a panic in exclusive systemlightyear::client::systems::receivehybrid::protocol::myprotocol_module::MyProtocol! Encountered a panic in system bevy_app::main_schedule::Main::run_main!
Will check more
it confirmed on two machines. but we got success before on other machines, so need to check them too , to be sure
Client "sagar" with id "22213" is trying to call 10.5.126.93:8000 2024-01-23T15:02:10.866601Z INFO bevy_winit::system: Creating new window "Thank you for your help! I want to complete it before February" (0v0) 2024-01-23T15:02:10.910139Z INFO lightyear::netcode::client: client connecting to server 10.5.126.93:8000 [1/1] 2024-01-23T15:02:10.910249Z INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "MacOS 14.3 ", kernel: "23.3.0", cpu: "Apple M1", core_count: "8", memory: "16.0 GiB" } thread 'main' panicked at /Users/sagar.yadav/.cargo/git/checkouts/lightyear-2cfb5e6660946fe3/0206f5f/lightyear/src/client/systems.rs:48:46: calledResult::unwrap()on anErrvalue: Io(Os { code: 65, kind: HostUnreachable, message: "No route to host" }) note: run withRUST_BACKTRACE=1environment variable to display a backtrace Encountered a panic in exclusive systemlightyear::client::systems::receivehybrid::protocol::myprotocol_module::MyProtocol! Encountered a panic in system bevy_app::main_schedule::Main::run_main! (base)
error looks similar for two machines. it happens after client window opened , then after two/three seconds app panics
It looks like just cannot find the remote IP?
@pine cape yes, but to be sure we need to wait up to tomorrow. To check with another pair of machines. Because there is information about the settings for machines are not the universal. Not everybody can connect to everybody.
So first we will check the another machines again, they were work correct in the past at least two weeks ago.
Then i will report
the machines have private access/passwords
@pine cape other machines works as expected. it was network or admin settings
probably just a firewall, with that said, lightyear should handle a failed route gracefully
it looks like unwrap() used at the moment is very raw solution. Probably later when library will formed properly to be v 1.0.0 etc all possible unwraps can be refactored to soft way , without just panic with closing the window
I forgot to crosslink here, but I deployed a new version which is wasm-compatible via webtransport. I made the examples available online here: https://cbournhonesque.github.io/lightyear/book/examples/title.html
The latest release (0.8.0) has an issue in its published crate where it depends on the 0.7.0 version of lightyear-macros. It might need republishing.
Heyo, I'm trying to learn the basics of Lightyear at the minute but it seems both the tutorial and the example is out of date and I can't fully figure out what the errors are saying, any help?
use bevy::prelude::{Color, Component, Deref, DerefMut, Entity, Vec3};
use derive_more::{Add, Mul};
use lightyear::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Direction {
pub(crate) up: bool,
pub(crate) down: bool,
pub(crate) left: bool,
pub(crate) right: bool,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Inputs {
Direction(Direction),
Delete,
Spawn,
None,
}
impl UserAction for Inputs {}
#[derive(Message, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Message1(pub usize);
#[message_protocol(protocol = "MyProtocol")]
pub enum Messages {
Message1(Message1),
}
#[derive(Component, Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct PlayerId(ClientId);
#[derive(Component, Serialize, Deserialize, Clone, Debug, PartialEq, Deref, DerefMut, Add, Mul)]
pub struct PlayerPosition(Vec3);
#[derive(Component, Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct PlayerColor(pub(crate) Color);
#[component_protocol(protocol = "MyProtocol")]
pub enum Components {
PlayerId(PlayerId),
PlayerPosition(PlayerPosition),
PlayerColor(PlayerColor),
}
#[derive(Channel)]
pub struct Channel1;
protocolize! {
Self = MyProtocol,
Message = Messages,
Component = Components,
Input = Inputs,
}
pub(crate) fn protocol() -> MyProtocol {
let mut protocol = MyProtocol::default();
protocol.add_channel::<Channel1>(ChannelSettings {
mode: ChannelMode::OrderedReliable(ReliableSettings::default()),
direction: ChannelDirection::Bidirectional,
});
protocol
}
Give me a moment on the errors, forgot you can't copy paste in zellij..
I suppose I could just go back to the previous version while learning Lightyear but I feel like I should learn the up to date stuff since I'd run into this later anyway
Ah it might be because it uses the version 0.7.0 of lightyear-macros, as @lunar robin mentioned above. Re-publishing now
Ah thanks! That was it
Deployed 0.8.1, where lightyear now depends on the correct version of lightyear_macros... π
By the way all examples should be up-to-date, I updated them yesterday. (the tutorial in the book might not be though)
Yeah the example seems to be up to date, and the tutorial being out of date is fine as I can just compare it to the example
This error just made me think the examples were out of date as well
Preview of next feature: bandwidth limiting and priority management!
You can set a priority:
- on the channel
- on a message: the final priority will be
message_priority * channel_priority - on an entity: the final priotity will be
entity_priority * channel_priority
To avoid some messages/entities never getting replicated there is an accumulation system: every entity that gets an update/action sent has its priority reset 0.0, all entities who wanted to send a message but couldn't get their priority accumulated.
In the example, the further away a row is from the center, the higher its priority.
(center row has priority 1.0, edge rows have priority 7.0, so are updated 7.0 times more often in case the bandwidth is limited)
@pine cape if you decide to move transport library into a separate crate - ping me, I will be happy to test it.
Sure! I also just finished creating Traits for the "Connection" abstraction on top of the raw transport.
The only implementation currently is the netcode standard (which is identical to what renet does)
Do you think there is anything missing that you need? https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/connection/client.rs#L13
https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/connection/server.rs#L9
One problem is that I return a ReadWordBuffer that is heavily tied to bitcode and does a copy. Maybe I can provide an api that is byob (bring your own buffer) so you can do whatever you want with the raw &[u8]
Yes, it would be better to return just &[u8]. bitcode is nice, but:
- It doesn't have streaming API which makes it slow as hell for replication.
- It doesn't support DeserializeSeed which is needed for serializing things like
Box<dyn Reflect>.
what is deseriliaze seed? do you have a link?
It's a statefull deserialization:
https://docs.rs/serde/latest/serde/de/trait.DeserializeSeed.html
DeserializeSeed is the stateful form of the Deserialize trait. If you ever find yourself looking for a way to pass data into a Deserialize impl, this trait is the way to do it.
In case of Reflect it allows to pass app type registry.
ReadWordBuffer is a wrapper around the bitcode reader that keeps around the original buffer inside the struct (in a self-referential manner) so it can do streaming deserialization: https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/serialize/wordbuffer/reader.rs#L25
But bitcode can't do streaming serialization, so streaming deserialization is not quite useful, unfortunately.
This is why we use bincode:
https://docs.rs/bincode/latest/bincode/fn.serialize_into.html
Serializes an object directly into a Writer using the default configuration.
Which does support it.
Where do you use streaming ser/de?
For me the only use is to be able to deserialize the packet (1200 bytes) in small chunks
When I serialize components
Not only components, everything.
Instead of creating a struct, we stream data into buffer directly.
What do you mean by streaming serialization? Being able to serialize components into a buffer not in one go?
It's like 4x times faster according to our benchmarks.
I think I have that via this Write wrapper: https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/serialize/wordbuffer/writer.rs#L21
I can write a serialize a single component (instead of creating an entire struct ahead of time)
When we iterate over the world, we serialize components, entities and other needed stuff directly into a buffer without intermediate struct.
But it will create an intermediate buffer, right?
Since bincode doesn't have a streaming API
In bitcode every call returns a new Vec.
it's a fork of bitcode, i'm not using the public api
but anyway yes I can try making the changes
I will probably provide a byob API rather than returning &[u8]
Ah, I see. So you have a streaming API internally, got it.
I would be great.
Also you don't use bytes, right?
bytes, the crate
I use bytes internally in the packet-manager that implements various channels
(because i need to be able to cheaply clone the messages that have to be re-sent reliably)
but maybe you don't need that part? do you need reliable/unreliable channels?
Yes, I need only sending messages over channels.
But I just curious. Renet have bytes in public API to allow users to send the same message to multiple clients without copying.
ah so you do use channels, not just the raw recv/send api
Yes :) NetServer is a lover level abstraction?
Renet have RenetServer with a similar API, but also with channels
Yes there's 3 levels:
- Transport: the basic recv/send functions that are basically raw bytes
- Netcode (NetClient/NetServer): also maintains an abstract "Connection" via "ConnectToken"s
- Channels: handles ordering/fragmentation/reliability.
Maybe i'll publish them as 3 crates
Got it. Looks like I need only channels one.
but channels doesn't do anything by itself, it needs the other two to actually send bytes, and being able to swap out transports. we'll see π
Got it, so it structured a bit differently.
Finally we passed the project , even under the extra control from ... administration ... after slander of the local students dream team: prisoned drugdiller(+narcoman) and his new ... friend( who like discussions about hitler regime advantages and benefits of war etc) ... which on high tone , complained close to scream that our group do not have the game at all . And nothing for them, they came out dry from the vomit.
Thank you all for assist!!!
And again no time, and stressed schedule continues π π¨
lightyear looks really awesome! It sounds like it would solve all of our problems! I am trying to put a proof of concept together for an open-source (M)MORPG. In general my PoC it is supposed to be a simple cross-over of the leafwings and boxes demo... But somehow my movements do not replicate to the server.
I spawn the character like this: https://github.com/Ablu/mana_combat_demo/blob/0c96bcfc55cb7664394cde3807ed04c1a15386f0/src/client.rs#L171-L180 and can move locally fine. Syncing is defined at https://github.com/Ablu/mana_combat_demo/blob/0c96bcfc55cb7664394cde3807ed04c1a15386f0/src/shared/protocol.rs#L38-L43. But the position does not sync back to the server (and from there back to the other clients). But I do not really see what I do different compared to the leafwing demo? I also do not really now how to further debug or dumb this down further. So any hints would be welcome π
Hi, thanks! π
You might be missing the LeafwingInputPlugin on the server-side: https://github.com/cBournhonesque/lightyear/blob/main/examples/leafwing_inputs/src/server.rs#L89
So the server does not receive the client inputs
I think one way to make the example simpler as well would be to not use "pre-predicted" entities, which can be a bit complicated. Instead, you could just spawn the entity on the server (with the correct prediction/interpolation targets), and then add the InputMap on the client-side by reacting to the EntitySpawn. You can find an example here:
- on client-side, add the InputMap: https://github.com/cBournhonesque/lightyear/blob/main/examples/interest_management/src/client.rs#L153
- on server-side, spawn the entity when a player connects: https://github.com/cBournhonesque/lightyear/blob/main/examples/interest_management/src/server.rs#L156
Ahrg... Hit and sunk! Thanks a lot!
Yeah, I realized that somewhere half way through copying the leafwing example. I will want pre-predicted entities eventually, but yeah. That will be a lot easier. Did not notice that interest_management did this, thanks!
That would have probably been a lot easier to sample to start from interest_management is also on the list of things I want to integrate into the demo... But this way I already leant a lot from my other mistakes so far π
Starting from leafwing_inputs is probably fine as well, it's just a bit more hard-mode! π
Especially this part: https://github.com/Ablu/mana_combat_demo/blob/0c96bcfc55cb7664394cde3807ed04c1a15386f0/src/server.rs#L112
it took me a while to get that the leafwing ActionState was getting replicated from the server to client, and then copied from the Confirmed entity to the Predicted entity, which was overriding the client inputs!
Don't hesitate if you have any questions/feedback
If you check the git history you will see that I first omitted it and debugged for a while before adding it π
Thanks! So far I mostly struggeled with also having to learn bevy next to lightyear. Took me a bit to piece the documentation for replication + prediction together, maybe I will be able to find a calm evening to extend those :). Otherwise the feature set already looks pretty impressive. The missing line above probably costed me a few hours, but the rest was relatively straightforward (though I still got some cleanup to do of course).
I wonder: Is there a way to interpolate towards "predicted" too?
Btw you shouldn't have to define your own interpolator (https://github.com/Ablu/mana_combat_demo/blob/0c96bcfc55cb7664394cde3807ed04c1a15386f0/src/shared/protocol.rs#L24), since I think here you can just use the default LinearInterpolator
I'm not entirely sure what you mean by interpolating towards "predicted"; do you mean that you want rollbacks to not 'snap back' instantly to the corrected position?
Yeah, I realized that too at some point, but then readded it out of desperation while debugging π
Hm, I had the impression that the interpolation interpolated between the last known position and the new confirmed. But maybe I need to do new research when back at my desk later π
Interpolation will interpolate between the last 2 "confirmed" positions. You have some regular server snapshots coming in every 100ms and interpolation will smoothly interpolate between the most recent snapshots. (the interpolation timeline is slightly in the past because we need to wait until we have at least 2 server snapshots)
Ok, then I understood it correctly. I plan to experiment with predicted monsters. I can predict them fairly well, but little desyncs may still happen. But I guess I can do my own interpolation if need be π
Ah, desyncs on predicted entities are already handled for you! Take a look at this chapter: https://cbournhonesque.github.io/lightyear/book/concepts/advanced_replication/prediction.html
Actually in the leafwing_inputs example, everything is predicted, included other players!
Since you don't have access to other players inputs in real time, but only with some delay, there are constant mispredictions. So the client is rolling back very often and replaying the last few frames.
In your case of predicted monsters; you will just need to replicate them with a PredictionTarget and prediction with rollback in case of desyncs will already be done for you. The rollback code is available here: https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/client/prediction/rollback.rs#L26
(it's probably the most complicated system in lightyear though)
Yep, looks like it all just works :). Thanks a lot!
This is likely my inexperience of Rust traits, but I'm having some issues with the input sets (both vanilla and Leafwing) requiring the Eq trait. I am trying to pass float values as part of a Vec3 across the network as a client input (click to move functionality). Is there a best way to approach this? I don't want to have to fake the Eq implementation, but the input interface in Lightyear requires Eq.
112 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, Hash, Reflect, Actionlike)]
| -- in this derive macro expansion
113 | pub enum PlayerActions {
114 | Travel(Vec3),
| ^^^^ the trait `std::cmp::Eq` is not implemented for `bevy::prelude::Vec3`
Hm let me take a look, maybe the Eq bound is not actually required.
It looks like Actionlike itself from Leafwing requireds Eq: https://github.com/Leafwing-Studios/leafwing-input-manager/blob/main/src/lib.rs#L88
How come you're passing Vec3 as inputs? Normally input values should be tied directly to the physical controller; so for a joystick it would look like this: https://github.com/Leafwing-Studios/leafwing-input-manager/blob/main/examples/axis_inputs.rs
I will remove the Eq bound for the non-leafwing inputs though, they don't seem required
That's a shame. I dug into Leafwing and did see that it has an impl for Eq somewhere in there.
I'm needing to pass Vec3 as a coordinate in the world for point-and-click. I'm raycasting on the client, then passing the in-world result as the input. From this, the client and server perform navmesh pathfinding to agree on the player's movements. I think that the Vec3 is the simplest form in which I can describe such an input.
I did think I could sync an entity to the raycast location and have the game simulate the pathfinding between the player and cursor positions in world, but I felt that it was expensive to loop through inputs, then players, then cursors, and it seemed wasteful bandwidth-wise.
There is probably a more elegant way to structure the solution than I've yet come up with.
Can you ask on the leafwing channel how this kind of usecase should be supported? I agree that it can be hard to only pass raw cursor positions
In any case, I removed the Eq bound for the vanilla inputs, so you can try to use that. It will be on the main branch though
Maybe I should issue a new release ahead of bevy 0.13; i've changed a lot of stuff
Shall do. I can't imagine I'm the first to try this kind of input with that library.
Managed to re-find the Eq impl in leafwing-input-manager. It's for the axislike implementation where axes have f32 values. https://github.com/Leafwing-Studios/leafwing-input-manager/blob/15958606b68b45275790927d2b6692287e66cc5d/src/axislike.rs#L186
@pine cape I've made an attempt at a further commit fixing up the crate for 0.13. https://github.com/cBournhonesque/lightyear/pull/155
thanks, let me take a look!
I see your PR result now has the bevy_asset import issue that I ran into.
I'll have to try; i'm not too worried about leafwing, as they are still upgrading to 0.13
The main problem I have is that the example panics when I run it: https://discord.com/channels/691052431525675048/1209022400369532958
Seems to be bevy_egui_inspector causing issues. If I disable that, the simple_box example works. Just seen you've already arrived at this in the other thread
Yes! strange that it doesn't give any compile errors but just panics like this at runtime; it's pretty error-prone
Looking at the trace, I initially thought it might be obscured by the async context, but yeah, it just seems to be some subtle error in bevy_egui
ah i wanted to add Children and Parent directly to the ComponentProtocol to be able to easily replicate hierarchies without introducing new wrapper structs, but it looks like Parent/Children are missing some traits (Clone, Serialize, etc.) sigh
Released a new version! https://discord.com/channels/691052431525675048/1209652689122758707
@unkempt vigil @keen crown I opened a new PR for steam support, might be interesting to you both!
I'll add a steam-based example using app-id 480 (the public app id); hopefully it's not too painful to get something working
ah i can't test because my work laptop apparently blocks steam π
if you need a 2nd person to test steam connection let me know. im in gmt+10 but my hours are.. flexible π
Thailand?
I mean you could try now by running simple_box example with cargo run -- server, but I doubt that it would work on a first try
are there other features/things to improve that are important to you?
Sydney
I meant if you wanted to try a multiplayer connection over steam between us to test the integration, sorry could have been more clear
ah sure, will let you know, thanks!
Just because steam is annoying you need 2 actual steam accounts running on separate computers to actually test it π
really? wow
There is on exception when you have a SteamGameServer you can login anonym per steamcmd and start it, with that you are able to join it on the same pc. But i think the game must be released to access it anonym π
Ah ok, I paid the steam fee so i can create a test app otherwise
ok i have something that compiles, but I get a message like
Caching Steam ID: xxx [API loaded no]
not too sure what it means
I want to spawn an entity that has a "despawn_at_tick". But it looks like there is no API available to add to an Tick? What would be the best way to have tick-based timers?
Ah, I am stupid, I just need to turn it into a Tick before adding...
I'm not sure what you mean exactly by "adding to a tick"?. You can use the TickManager to access the current Tick info.
You want to add a Component that says "despawn in X ticks"?
Yep. I just briefly got confused by the wrapping and subtracting returning a signed. But works great so far π
@pine cape do you have any thoughts on supporting multiple transports in a single server ?
For example, a player runs the client & server but the server is a separate bevy instance. They would connect to it via channel, whereas their friends would connect through eg Steam.
Or a dedicated server would allow "crossplay" via allowing Steam users to connect easily, and UDP for non-steam clients.
yep that's something I want to support, there is an issue for this actually. I can look into that
Also I could take a crack at updating the vendored bitcode crate (just made a github issue) but it would be in a few days. Did you need to modify it much ?
what do you mean by "a player runs the client & server" btw? that the player is acting as a server? Would that be by launching 2 separate processes, or updating lightyear to be compatible with a player acting as server + client?
Oh I already fixed the bitcode issue; i'll push soon
It's a common technique to have the game server support being run as a dedicated server process (separate .exe etc), and in-process for easy of use/single player. This is used by Terraria, Valheim, Minecraft etc.
How this usually works is the server is a library that is embedded in both the dedicated server binary and the game binary. When a user hosts a game from the game client, it starts up a server instance in-process and connects to it (either via something like channels, or even over the network stack like localhost).
It avoids a host of issues where the server has to if/else if it's also running the client. Keeping things cleanly separated/separation of concerns etc.
In a bevy game, my expectation is that when a user starts singleplayer/hosts a game it would start up a new bevy App in a separate thread.
The docs are back! I issued a new release: https://github.com/cBournhonesque/lightyear/releases/tag/0.11.0
(i had to create a new major version because it includes a small breaking change)
That's interesting, it would be cool to have an example that showcased this! So it's simply a matter of creating two threads, one for the client and one for the server? I have a benchmark here that uses local channels as transport, so it should be doable!
Yep I could look into an example.
For full effectiveness it would need support for multiple server transports. If I have this model and want to use Steam networking for example, it doesn't really make sense to connect to yourself through Steam. I don't know what would happen if you tried, but I suspect it wouldn't work as the client & server instances on the same machine would both try to advertise themselves as the same player.
The other transports could work with single transport only, eg UDP, as it would be fine to connect to "yourself" in that instance. But it is sup-optimal to roundtrip through the network stack if it's not actually needed.
Thanks! Yeah i'll look into the multiple networking support!
I'm not too familiar with steam, i saw there were options for p2p/relay/dedicated-server.
I'm mostly working with the mental model of dedicated server, but it would definitely cool if we suddenly supported p2p by having one of the clients act as a host (with local channel) and use p2p steam connections to other players
For the purposes of purely networking the distinction between p2p & dedicated-server on Steam is not that important. You could have a dedicated server running an instance of the game that thousands of clients connect to through Steam "p2p". Or you could have a player at home hosting a "dedicated server" through Steam Server api while they have the game open.
The reason Steam networking is valuable is because Valve operates a giant VPN, essentially π
- Easy to set up "Invite friend to game"
- No need to deal with players needing to port forward/hole punch etc
- IPs aren't shared between connected clients, can't DoS other players or the server
- Traffic is automatically authenticated, encrypted, and rate limited
So i tried running the simple box example from the stable release version of the repo but was met with an error about a certificates file not being found and it panicking on an unwrap due to that.
Was there some setup I'm supposed to do to run it?
It should work out of the box; do you have those certificate files in your repo? https://github.com/cBournhonesque/lightyear/tree/main/examples/certificates
a generate.sh is present nothing else
am i supposed to run that?
that's strange, maybe just pull the latest changes from main ? the files are there in git
otherwise yes you can run generate.sh to create new credentials.
You should then run the server first, and then replace that line on the client: https://github.com/cBournhonesque/lightyear/blob/4e2dceab75d17d00a57d7e2280ad47df7e0fa7e3/examples/simple_box/src/client.rs#L35
I put some instructions at the bottom of this readme: https://github.com/cBournhonesque/lightyear/tree/4e2dceab75d17d00a57d7e2280ad47df7e0fa7e3/examples/interest_management
(the certificate has been added to git 4 days ago)
ahhh
i initialy tried the main but had 5k errors so i fell back to the release version instead
oh i see; let me know if you have errors still!
@unkempt vigil @stiff quiver I added cross-transport support in this PR: https://github.com/cBournhonesque/lightyear/pull/169
I've tested it with one client connect with UDP and another one connected via WebTransport!
With this I think we'll be able to support the "player runs as client & server in the same process" mode that you were mentioning!
Yoo thats sick
@pine cape for args cant u just do multiple --transport [transport] args and then parse that into a vec?
iirc clap can do smth like that
cant review atm tho, maybe later or tomorrow
Thats what i do right now, but how there might be specific transport-level params (for example the port to use)
Could have a toml config file or something
what about envs
i don't really like env variables for that either
Having to manage a separate config file sounds just as annoying tho hmm
Perhaps just dont have a cli or smth like that at all and just do the config inside the code?
well the config file lets me have hierarchical values, which is what i need
I'm trying to use a config file, looks like this:
Settings(
client: ClientSettings(
inspector: true,
client_id: 0,
client_port: 0, // the OS will assign a random open port
server_addr: "127.0.0.1",
server_port: 5000,
transport: WebTransport(
local_port: 0, // unused for clients
),
),
server: ServerSettings(
headless: true,
inspector: false,
transport: [
WebTransport(
local_port: 5000
),
Udp(
local_port: 5001
),
WebSocket(
local_port: 5002
)
],
)
)
which I think is fine
i'm just trying to get the file loading in wasm
just include_str! it imo
Config files dont have static validation tho
thats another important thing to consider
Sure neither does a cli but still
include_str works, thanks
Random thought about WebTransport and Netcode. If I'm understanding correctly, Lightyear uses the same Netcode crate/protocol to communicate over a raw UDP "connection" and a WebTransport connection via its datagrams and streams?
If so, doesn't that approach duplicate a bunch of functionality? WebTransport already provides encryption and certain transport guarantees that Netcode duplicates (as far as I can tell from the Netcode 1.02 spec document).
Yes that's correct. I just set it as Transport because that was the easiest thing at the time (i didnt have an abstraction for a 'connection' in the code), but it might be possible to use WebTransport directly as the 'connection' layer without using the netcode protocol
So im trying to conceptually understand lightyear
Lemme know if this is about right
the protocol is basically how everything is sent/received and act like event readers
Messages are like bevy events
Inputs are like event writers
And components are data that should be sent in messages on specific inputs?
Is that about right?
The protocol contains every information that is shared between client/server (channels, input, components, messages).
Every piece of data is fundamentally a Message. Anything that can be serialized+deserialized+cloned can be a Message
- Messages that are also components are defined in the
ComponentProtocol, and lightyear makes sure that those components will be replicated automatically (world replication) - Messages that are client inputs (user actions like keyboard taps, mouse clicks, etc.) have some special handling (for example they are networked with some redundancy to not lose inputs even in case of packet loss) and will be
Protocol::Inputs - other messages will just be inside the
MessageProtocol
i freshly cloned the repo and im getting this error from rust analyzer
doesnt actually stop from compiling and running tho
also lightyear doesnt shutdown properly? it says webtransport but i didnt use the webtransport commands
this is the simplebox example
By default the examples are using webtransport
Hm i use rustrover and don't have any errors. Does the example run correctly?
Yes, but i don't get why the error exists, the examples with one of/or both of the leafwing input types don't have this error, its only on the ones that down have any leafwing in it
The entire repo has a total of 254 warnings and errors from rust analyzer
Probably rust analyzer is parsing the code with the feature 'leafwing' enabled (the feature is disabled for this example)
I'm back from vacation; I just released a new version with ListenServer mode + multiple transports! #crates message
All examples can run in "listen server" mode via cargo run -- listen-server
@keen crown I wonder if you could help with the steam integration? https://github.com/cBournhonesque/lightyear/pull/158
Testing is very easy (I added instructions in the PR description).
I don't exactly understand what is not working; the client is stuck in the Connecting state, but the server does not seem to receive any of the connection requests.
Test steam connection by going to the simple_box example: cd examples/simple_box
Run server: cargo run -- server
Run client: cargo run -- client
(update settings in assets/settings.ron)
(maybe @brittle cloak you also have an idea? this is how I initialize my listen socket: https://github.com/cBournhonesque/lightyear/pull/158/files#diff-b3cc7007e901d8b7737b4fc02528218b326accc78ec5289403b8456fe3b75edeR87
It looks like the server-side code from steamworks-rs is a bit lacking..)
hi, i wanted to look into it as well as i did some things in a previous project with steam too and got it working there, i'm on the most recent commit in your pull request but can't get it to compile
error[E0507]: cannot move out of dereference of `bevy::prelude::ResMut<'_, lightyear::prelude::server::ServerConnections>`
--> examples\simple_box\src\server.rs:65:27
|
65 | for mut connection in connections.servers {
| ^^^^^^^^^^^^^^^^^^^
| |
| value moved due to this implicit call to `.into_iter()`
| move occurs because value has type `std::vec::Vec<ServerConnection>`, which does not implement the `Copy` trait
|
note: `into_iter` takes ownership of the receiver `self`, which moves value
--> C:\Users\zwaze\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\core\src\iter\traits\collect.rs:268:18
|
268 | fn into_iter(self) -> Self::IntoIter;
| ^^^^
help: consider iterating over a slice of the `std::vec::Vec<ServerConnection>`'s content to avoid moving into the `for` loop
|
65 | for mut connection in &connections.servers {
| +
For more information about this error, try `rustc --explain E0507`.
error: could not compile `simple_box` (bin "simple_box") due to 1 previous error
* The terminal process "C:\Users\zwaze\.cargo\bin\cargo.exe 'run', '--package', 'simple_box', '--bin', 'simple_box'" terminated with exit code: 101.
* Terminal will be reused by tasks, press any key to close it.
anything that i missed?
sorry, I forgot to push a change; how about now?
yep that works, altough rust analyzer still complains about a lot in the vendor directory lol
sorry, first time running lightyear.
i assume when i just run cargo run -- client it doesn't start with the steam config?
what do i need to do to make it run using steam networking
ah wait, i found the settings.ron
yes sorry; you would have to uncomment the lines 22-25 here: https://github.com/cBournhonesque/lightyear/blob/cb/steam/examples/simple_box/assets/settings.ron#L22
and comment out the previous server_port and transport
Currently not able to get on the pc sorry. It sounds like you are not running the steam callbacks eg. https://partner.steamgames.com/doc/api/steam_api#SteamAPI_RunCallbacks
The callbacks have to be run? I didn't see them in renet: https://github.com/lucaspoffo/renet/tree/master/renet_steam/src
Yes its not your responsibility, because the user should decide how often and from where he wants to call it (its thread-safe). But to have a working example: https://github.com/lucaspoffo/renet/blob/master/renet_steam/examples/echo.rs#L60 you need it
So for try_receive_events to work (https://docs.rs/steamworks/latest/steamworks/networking_sockets/struct.ListenSocket.html#method.try_receive_event) I have to call run_callbacks ?
A socket that will continually listen for client connections. Call events() to receive incoming connection. You should regularly check for events and answer ConnectionRequests requests immediately or the socket will appear as unresponsive to the client.
yes for everything network related and of course other callbacks
The Steamworks plugin should automatically call run_callbacks for you.
AFAIK the bevy steamworks plugin runs it automatically, but lightyear (as far as i can tell) just uses the rust bindings for steamwork, in which run_callbacks should be run manually
got it working
created a pull request to be pulled into your steam branch: https://github.com/cBournhonesque/lightyear/pull/182
not sure if what i did is optimal, but this allows you to easily see the changes i did
Awesome, thank you so much! I have a hard time testing this because my company laptop doesn't allow me to run steam, and I have trouble coding in windows.
By the way what's your read on this? https://github.com/Noxime/steamworks-rs/issues/155
It doesn't seem to be possible to use the ServerManager anywhere in steamworks
I'll look into that a bit more later, not familiar enough with the code base of steamworks-rs
Thanks; I'll look into p2p modes later as well. They might be useful in listen-server mode with one player acting as host, so other players can easily connect to the host with just the steam id
thats something i was thinking about as well.
is my understanding correct that with the current approach we'd still have to do port forwarding as it doesn't actually connect over steam but still directly over ip?
i've done p2p before already with steam invites and so on, so i could possibly help here
Lightyear was created with mostly dedicated servers in mind, so yes, in the current approach the thinking is that the library user provides a dedicated server with a publicly accessible ip so there's no issues with port-forwarding. (while still using the steam networking code)
But if we want one of the players to act as host, I think it might be useful to have steam act as a relay where only the steam id is needed yes. It might just be as easy as switching to https://docs.rs/steamworks/latest/steamworks/networking_sockets/struct.NetworkingSockets.html#method.create_listen_socket_p2p and https://docs.rs/steamworks/latest/steamworks/networking_sockets/struct.NetworkingSockets.html#method.connect_p2p
so that clients talk to the 'host' without ip address
Access to the steam networking sockets interface
ah i didnt know lightyear was created with dedicated servers in mind!
but it is indeed as easy as to switch to those two methods :)
i personally would very much support to having a p2p "Mode" or something in which we can do it like this, maybe even with steam invites working :)
I think p2p would still be limited to having one player act as host (listen server); not a pure p2p mode where all clients talk directly to each other (lockstep). Would that be fine?
(I think? I'm not too familiar with p2p)
yes that is exactly how it works. one client still acts as a server, receiving all messages and forwarding it to the other clients
in an older project where i used steamworks, i also used their lobby system, which handled automatically who the host is and such
but i guess it's not planned for lightyear to be using this feature from steam api?
probably too much incompatibility with the other networking types?
altough it makes setting and sharing some general settings super easy, just set some lobby data and everyone gets informed about them
i'm not opposed to it; several people have asked about this (p2p with one player as host) so if i can get it working without dedicated servers i'll try!
i think that would be really cool!
I might also look into it this weekend
Lightyear was created with mostly dedicated servers in mind
I'm curious about this, are there any fundamental design restrictions to be aware of if trying to use lightyear without a dedicated server?
Not necessarily, it's just that I didn't really think about other networking topologies when building/testing lightyear.
I tried all the examples in listen-server mode and it seems to work perfectly fine though.
But i'm not sure if something like multiple clients with p2p connections without any client acting as 'host' would work; lightyear expects the presence of a Server.
Yeah I think "full-mesh" p2p is a niche use case
isnt this the same as dedicated? you just need a method for peer discovery?
It's acceptable for small lobbies
And requires a non server authoritative structure
But i'm not sure if something like multiple clients with p2p connections without any client acting as 'host' would work
yea youd need a consensus method to replace the authoritative nature of lightyear
haven't worked with lightyear yet, but i have it in my mind for a while now and at some point my project gets to a point where i need something for multiplayer. just wanted to know if it's currently possible to have the "server" also be a player?
just like discussed before with steamp p2p, but generally.
so no dedicated server, but just the player hosting the game, is that already doable?
is that what you mean with listen-server mode? not sure what that is
Yes π try any example with cargo run -- listen-server
The client and server run on the same machine, but in different threads.
ahh cool!
but that means i can't configure a client to be a host at runtime?
so let's say i start my game and i have 2 buttons. host and join.
when i click on host, the game configures itself to be a "server", and when clicking on join it's a "client"
Hm i think it's do-able, but you would need to do that prior to starting the lightyear plugin probably?
i.e. there would be a 'lobby' stage in which the user selects the host, etc.
And then when they click start, you use that information to create the lightyear plugin with the correct information (NetConfig, etc.) and run it
i thought adding plugins at runtime isn't supported by bevy?
or did i miss something?
I was thinking maybe of using something like sub-Apps, or separate apps, etc.
But yes that's definitely something to investigate.
In general the io stuff doesn't really matter before connect and start are called; so it might be possible to add functions that let you update the config at runtime before you call connect or start
ah never worked with sub-apps and co, so not quite sure what i can do with those
In general the io stuff doesn't really matter before connect and start are called; so it might be possible to add functions that let you update the config at runtime before you call connect or start
that would of course simplify things :)
I opened an issue for this: https://github.com/cBournhonesque/lightyear/issues/183
cool, thank you!
@pine cape Is it possible to have a listen-server without a separate server app? I'm thinking that the bevy scheduler would be happier if you weren't running two apps at the same time, and it might also be easier to avoid recomputing the same states twice, once on the host-client and once on the host-server
I'm not sure; it would be a pretty big architecture change. There might be some server-specific entities, I'm not sure how the server entities and client entities can co-exist in the same World
I was thinking about this as well, also regarding the issue you created here.
I feel Like just having Systems designated to be ran when you're a client and Systems to be ran when you're a Server is something.
We could have a state, that says if we're "unconfigured", "client", or "Server/Host".
And then the right System runs if we're in the right state. And maybe clean up Systems run whenever we Change the state.
Like this we could easily switch between Different modes without much hassle.
And we could also have a "listen" state in wich we could Run both the Systems designated for client and Systems designated for Server. And use systemsets to Order them reliably.
At least in my head, i'm not too familiar with the codebase.
Yeah this is probably not a trivial thing to change, but it does seem slightly awkward to use lightyear in host-client mode at this point. bevy_replicon has implemented this fairly nicely I think, where client and server can run separately or in the same World, or even entirely without a server. replicon has a much less ambitious feature set though
Oh? Do you have an example of client and server running in the same world with replicon? I'd be interested in taking a look
I actually think it is cleaner to maintain 2 separate Apps for client and server world if client/server are running on the same machine
There's no official example of it, but it's mentioned in the readme. You insert a server resource and a client resource and I think it just works? From my point of view the drawbacks of separate apps would be 1) Less efficient scheduling, though I haven't benchmarked this at all 2) Host-server and host-client need to calculate the same thing twice, although I suppose this could be solved using some thread-safe cache shared by both apps 3) The "networking support is just a resource" idea makes it simple to switch roles between host and client at runtime
Actually, I think it's also possible to do this with lightyear, Server and Clients are just plugins so you can just add both on the same app. I could try to do that in the examples.
As you point out, there's also the risk of server and client stepping on each others' toes by accidentally sharing resources or components, so I'm guessing there are some tradeoffs here
Actually I'm wondering how this works with e.g. time, since client and server will usually be simulating different ticks. Maybe it's fine if you're careful
(Can I ping @lapis skiff here? What's the idiomatic solution here using an ECS?)
One of replicon maintainers here :)
All our examples show how server can also be a client. Try running any example, you can select the mode from CLI.
But no, you don't need to insert RenetClient. Crate designed in a special way to let server act as a player. So no extra overhead of running client and server on the same machine for singleplayer or listen server mode.
if i remember right minecraft does something just like that
My bad, I was only skimming the code π
But when the one player is running a listen server, wouldn't that player also need to run client logic in order to do e.g. prediction/interpolation?
It will be able to run client logic, it's managed via run conditions. But for listen server you don't need to predict/interpolate.
For more details about how prediction/interpolation will work in this case take a look at bevy_replicon_snap. It have examples similar to replicon's.
Hmm the host-client should still have to predict/interpolate, since it does not have up-to-date knowledge of other clients' states, right?
Ah, you mean other players. Yes, sure, it will run this logic!
Right, but it can run this logic without having a networking client running
Yes.
Actually, no, I'm no sure... What you predict when you are listen server? You predict yourself and interpolate other players on client. But you don't need any of it on server.
Maybe you mean extrapolate? It's not a common technique, but third-party crates can run this logic on listen server if they need. That's what I wanted to say. In replicon we delegate this to third-part crates. But I'm not the author of bevy_replicon_snap :)
From my understanding, and I could be wrong, but your host-server will be simulating the authoritative tick t, and your host-client is simulating t+n since that's what the other clients are also trying to simulate. In that case you're still predicting/extrapolating, even though your "ping" is 0ms
It is precisely all of this madness that I would like to outsource to lightyear!
Though I'm pretty convinced there is no library that will fully hide all the complexity here
Ok I got both client/server plugin running in the same app, but I have a problem with inputs.
- Tick = 10, client prepares inputs in FixedPreUpdate. Then everything gets sent to the server at PostUpdate.
- But that means that server receives the client's Tick 10 input at tick 11; so it just ignores it.
Possible solutions:
- maintain 2 different
TimeManager(one for the client/one for the server) even if both plugins are running in the same app - have some custom logic for the client that is in the same app: we don't actually need replication for that client, there is no need for prediction/interpolation. So the displayed entities will be the
Interpolatedfor other clients, and the entity itself (without prediction) for the local client. That's good but then there needs to be some custom logic, and ideally the code wouldn't change between listen-server or dedicated-server - some other solution?
Right, so (1) is what I was mentioning above where the server and client need to simulate different ticks in the same app. As for (2), I'm still convinced there is a need for prediction even if you are running in the same app as the server due to (1).
Yeah but ideally client/server would be able to simulate the same tick in the same app; client/server should be able to be in the same timeline since there's 0 latency
you still have latency from other players though, and your host-client should probably try to run at the same tick as other clients?
Nope, every client runs in its own timeline. The more latency a player has, the more it runs in the future, compared to the server timeline (i.e. when server is running t, the client is running t + delta)
The reason for this is that the only thing that matters is that all the inputs for all clients for a server-tick T should be received.
So a client runs at timeline t + rtt/2. When they press an input for their tick t, the message will take rtt/2 to reach the server, which will be able to apply it on server tick t. This means that for a server_tick t, the server will have received the inputs from all clients at tick t.
This also means that clients with high latency are more into the future, so they have more risk of bigger rollbacks if something goes wrong. (+ they interpolate states that are more in the past compared to their timeline)
A perfect client is basically on the same timeline as server
Prediction is needed for local client to execute actions immediately without waiting for confirmation from server. Not needed on listen server.
Interpolation is needed because server don't send you each frame. Not needed on listen server too since you simulate each frame.
But that means that the code for listen-server would be different from non-listen-server.
Listen-server: client inputs affect directly the server entity (which is not predicted or interpolated)
server: client inputs affect the Predicted entity
I don't really like having code that is specific to either mode
Okay, thanks for clarifying
It doesn't have to be different though, you could just have a predicted entity which is always equivalent to the server authoritative state?
Yes. It's optional, though. You still can have two apps.
Actually I know what to do.
- In non-listen-server mode, the client stores the inputs in a buffer, and the inputs get sent to the server via network packets, etc. The server reads the packet, emits
ServerInputEvent. DuringFixedUpdateit readsServerInputEventand then applies them to server-authoritative entities. - In listen-server mode, the client stores the inputs in a buffer during FixedPreUpdate. It also emits directly
ServerInputEventsinstead of trying to send events through the network, so the server can receive them instantly.
I just need a way for the server to know which client is in listen-server mode.
Right. So this ensures that all inputs arrive from the "client" at the same tick on the "server"
And my misunderstanding of prediction means that there's no need for the host-server and host-client to be simulating different ticks, which is even better.
yes, it's really good! Also with the previous 2-app systems, we would have a slight delay (of maybe 1-2 frames) for inputs, as the inputs were only getting read by the server on a future frame. This is much better, there would be 0 input delay
thanks for your suggestion
Actually I'm thinking a bit more about this mode where client and server are running in the same app.
While working on the implementation I realized there's 3 options:
A) client app and server app communicating via channels (what I have now)
B) client plugin and server plugin running in the same world.
- the Replicate logic might need to be updated, because we don't want to run prediction/interpolation on the local client
- the Rendering systems might need to be updated, because we want to render: the other players' Interpolated entities + the local player's entity (which doesn't have
Confirmed/Predicted/Interpolated). I could add another componentLocalto track the locally controlled entity) - we need a way for the server to know the local client's ClientId
C) and then I thought: in the 'local-server' mode, we actually barely need the client: there's no need for io or networking, prediction/interpolation are not needed, nor is sync, etc. So basically we could completely disable the ClientPlugin! The only thing that would run is the Server, where: - maybe we create a fake connection with a dummy io and a reserved
ClientId::Localclient id (maybe 0) - the lightyear ClientPlugin would not be added
- just need to add the input systems to write directly to the server's InputEvents for that fake clientId
- the problem is again that the systems that work on
Confirmed/Predicted/Interpolatedwould have to be adapted to work directly on the server entities
Actually i'm not sure if option C works, because of visibility/interest management; how do we make sure that the local client cannot see all other entities?
If you mean from a security/anti-cheat point of view, I don't think that matters if you're already running the server locally. And there should be no need for interest management to reduce bandwidth usage either. So I'm not sure what interest management is really contributing in this case
I was thinking of features like fog-of-war, but maybe they wouldn't get implemented via interest management; which is mostly used to save bandwidth?
In any case I decided to go for option B) for now; option C) requires too many changes
Yeah I think that's not entirely a networking concern
Having a Local component might make sense, but I think it could also make sense to have a passthrough Predicted component even for local components so that you can run queries on predicted components uniformly no matter if they're local or not, instead of having to write Or<(Predicted, Local)>
(Granted I'm still learning how lightyear works, so that might be misinformed)
This is what I ended up with:
- instead of using directly the server entities, we still replicate the server entities to the client (via the channel io) and spawn a new
Confirmedentity. (an alternative could be to add theConfirmedcomponent directly on the server entity) I think we need this for:- interest management
- it is possible that the
Confirmedentity should only contain a subset of components (The replicable components) instead of all server components
- for interpolation, in unified mode, I don't spawn a separate Interpolated entity, I just add the
Interpolatecomponent directly on theConfirmedentity. (since there are basically no cases where interpolation is needed) - for prediction, I tried doing the same trick where I don't spawn a new entity and instead add a
Predictedcomponent directly on theConfirmedentity, but I ran into issues:- there is still a delay to spawn the Confirmed entity (because the server sends information only every 100ms)
- maybe it's possible to do it if we don't use io at all and re-use the server entity (option C) but for now it seems easier to still keep the PredictionPlugin turned on
That sounds generally reasonable, but what do you mean that the server only sends information every 100ms? Doesn't that mean you'll need interpolation anyway?
oh you're right
I guess i'll do a first PR with option B: still keep io, still create Predicted entities and Interpolate entities. Easier to think about because it's basically very similar to the 2-app mode.
And then later it could be cool to make option C work: there is no IO between the local client and the server, the local client re-uses directly the server entities, the server-entity also gets added a dummy Predicted or Interpolated component if needed (so that client systems that query on Predicted/Interpolated work) but we only have a single copy of each entity
Another issue is the leafwing plugin; it might be possible to get it to work when inputs are attached to entities, but I don't it will be compatible with the inputs as resources mode
looks like you worked this one out, from the leafwing issue?
very excited about these changes, I'll be trying them out as soon as they land
yep! the next thing i'm trying to get working is pre-predicted entities.
(entities that are spawned ahead on the client, and then the authority is transferred to the server).
They don't work right now because it needs a client->server replication, which I've disabled
But tbh I still think that in most cases having the client and server in the same world is a mistake.
For example for physics-based examples I had to add special collision layers so that server entities don't collide with client entities. I think it's harder to reason when you have to worry about both server/client entities
I do like having a single timeline though
yeah I suppose this is only an issue when you do the copying entities strategy
Do you know of other games where client/server are in the same world? I think you mentionned minecraft before
That was someone else up in the thread. I can do some research though
It does seem more natural than e.g. running two copies of the physics engine in separate apps though
I guess it's nice to have the option
But from Shatur's comment above it seems that Replicon is doing your "Option C" above:
But no, you don't need to insert RenetClient. Crate designed in a special way to let server act as a player. So no extra overhead of running client and server on the same machine for singleplayer or listen server mode.
which then means that you don't need multiple collision layers in physics and no copying
Tbh I don't really know what that means in the context of the replicon examples.
Also I think it becomes harder when dealing with prediction/interpolation (and the fact that the non-listen-server code expects Predicted/Interpolated entities)
It sounds like in this case https://github.com/projectharmonia/bevy_replicon/blob/master/bevy_replicon_renet/examples/tic_tac_toe.rs#L254 they just use the server-plugin and don't use add a client-plugin at all. Which you can do in lightyear as well, but the example code will have to be adapted
Maybe i just need to rearchitect the examples?
Hm. I guess it is a bit of a no-op entirely from the client-side. No prediction or interpolation. No networking (not even via channels).
yeah, 0 client code at all
I guess the main complication is that you want to run input systems on the server
- thinking about how the normal dedicated-server client code (with Predicted/etc.) has to be adapted for this
yeah
but yeah ultimately this seems like the simplest way to have a listen-server
I'll still merge what i have now, as a proof of concept
sure!
No, both server and client plugins are present in the world.
In the linked code we just initialize the server resource.
We support disabling a client/server plugins, but I wanted to keep the examples simple while still showing how the mode can be changed at runtime.
I mean that since you don't add the renet-client plugin, it's similar to disable the replicon client
Renet plugin is present too. I only add RenetClient resource.
you know what i mean; the client plugin is basically not running since the client is not connected (because RenetClient resource is not added) (in the tic-tac-toe server example)
Ah, if that's what you meant, then yes!
@jade ember by the way were you able to look into the p2p steam setup? I might try to replace the current Steam connection (where the server is a dedicated server) with a p2p approach where the server has a steam id
I didnt look into it with lightyear yet, sorry. quite a bit todo right now because of finals :)
but i have worked with steam p2p itself before in my own project, so i might be able to help a bit there
I have a question about prediction/interpolation. In lightyear, it seems that multiple copies of entities are spawned with Predicted or Interpolated marker components. Is there a reason this design was chosen instead of having a single entity with e.g (Confirmed<Position>, Predicted<Position>) components attached to it?
Actually, now that I'm thinking of it, that might make interacting with plugins like xpbd a little bit awkward since it expects just Position and not a Something<Position>
It's just that I found it useful when visualizing at the same time the 3 possible states (predicted/confirmed/interpolated) of an entity. As you said, otherwise there would need to be some custom logic to interact with Predicted<Position>, etc.
Honestly I didn't put too much thought on it, it might not be the best design!
@dull lion thanks for making the thread in the networking channel! I'm almost done getting a listen-server working with a single world. The trick is to approach it a bit like unreal, where mostly the serverplugin is running, and there's 0 networking/replication/prediction/interpolation from the client plugin.
I have 6 out 7 examples working
cool! you're so fast π€―
I added a few review comments
Good morning, I'm planning to try a switch from bevy_renet to lightyear and I'm wondering if there are any gotchas I should be aware of? I'm also curious if lightyear supports replicating only relevant or visible entities out of the box or do I have to use something like bevy_spatial on top if it for that?
Are there any examples of supporting multiple types of transport in a game? I'm interested in using UDP for native and WebTransport for WASM and I've only got experience with bevy_ggrs and bevy_matchbox which took care of this for me.
Not the maintainer but I can probably answer anyway. Lightyear has interest management (https://github.com/cBournhonesque/lightyear/blob/7134b29c4d4844a1438363330a1ea5e896498c11/examples/interest_management/src/server.rs) but you might still need a spatial data structure to keep interest up-to-date, depending on your use case. It also supports both native UDP and WebTransport for wasm
ah okay I did see the interest management but didn't fully grasp it yet, thanks for that.
it seems like the migration from renet shouldn't be too bad, just need to spend some time w/ the examples
my main reason for changing is to support wasm clients so hopefully I can figure that out
All the examples support multiple transports at the same time, you can see the list of transports used here: https://github.com/cBournhonesque/lightyear/blob/main/examples/simple_box/assets/settings.ron#L35
okay great, does some portion of the crate handle the build target selecting which transport to use or do I have to implement that?
Currently no, you have to specify manually which transport you intend to use on the client. For example in this function I read the settings to select the transport to use on the client-side:
ah okay that helps, thanks!
really nice work so far on the crate btw, I look forward to trying it out!
Awesome, let me know how it goes π
will do, I'm sure I'll be back with more questions once I get in to it π
@dull lion I merged the changes π all examples should be working in host-server modes.
I'm pretty about the fact that most examples needed only a very small amount of changes to work both in Separate vs HostServer mode.
The main change I had to make is that sometimes I would use the query
(Without<Confirmed>, Without<Interpolated>) to target Predicted entities on the client + server entities. This doesn't work in HostServer mode, because other client's entities will have Interpolated added to them on the server; the solution is to use Or<(With<Predicted>, With<Replicate>)> instead, which works both in HostServer and Separate modes.
I will release a new version as there has been a substantial number of breaking changes. The next version will focus on:
- steam p2p
- more runtime configurability (being able to change the host at runtime)
- more control over the transport (for example clients should be able to disconnect from the server)
Great work! Was this less hacky than the previous approach in your opinion?
Maybe there could be some premade type-aliases for (With<X>, With<Y>) to handle this correctly
Yes, way less hacky, I think. The only hacky thing is mostly 'fake' adding Predicted/Interpolated on server entities so that client plugins that expect those components still work (for example most input queries expect Predicted, most rendering queries expect Interpolated)
Otherwise yes the main footgun is remembering to use the correct filters; I might provide pre-made filter aliases as you suggested
Maybe if the premade filters are clever enough, you don't even need to add the Predicted/Interpolated marker components
also the wasm examples are somehow failing with Uncaught TypeError: Failed to resolve module specifier "env". Relative references must start with either "/", "./", or "../".
I don't really know what's causing this. @stiff quiver do you have any ideas?
Sorry no, im not up to date with stuff happening atm
This sometimes happens for me when I cancel a build and is fixed by cargo clean, or if you accidentally use Instant from std instead of from bevy/web_time
it does seem related to std::time, thanks
Released a new version with all the fixes/changes: #crates message
I've got a question regarding the steam integration, do you support the steam servers in anonymous mode? IE as dedicated servers?
Actually that's the only mode I support currently; you need to provide a server_ip to launch the steam server, but I want to support P2P mode
All the previous steam integrations I've seen are all peer to peer, and require a steam client to be logged in and running
That's excellent news, I'll have to whip up a proof of concept soon!
Normally you should be able to try with any of the examples by uncommenting this: https://github.com/cBournhonesque/lightyear/blob/main/examples/simple_box/assets/settings.ron#L45
However I think you might still need a steam client to be logged in on the dedicated server, as I'm using this to access the api: https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/connection/steam/server.rs#L68
Yup, that's what I was afraid of.
Thank you, @pine cape, for your work on lightyear! β€οΈ
I was deciding between bevy_replicon and lightyear for my first multiplayer project and wanted to use a client host. Lightyear didn't this, but now two days later it does! Fantastic job and major thank yous :-)
new update seems neat, especially the host-server option. is it strictly better than listen-server? or is there a reason other than legacy support that the channel-passing method still exists?
If you write your code with a dedicated server in mind (i.e. separate Client/Server worlds), you will have separate client/server plugins.
In the host-server mode, you only have one world (the server world), but you still want to run both your client and server plugin in this unique World.
As a "trick", I added the Predicted and Interpolated components to entities in the host-server world so that your existing client plugin (which probably query entities based on this trait) still works, but it is not guaranteed.
In short, the code you wrote for dedicated-server will always work for listen-server (since it's still 2 separate worlds), but it might not work right of the bat for host-server (as it's 1 unique world, so you might have to make adjustments)
I've tested that all examples work in host-server mode, but there might be situations I haven't thought about
It looks like lightyear uses several ChannelInternal channels for most of what I'd normally be dealing with. When does defining my own channel become needed and how does lightyear decide when it gets used?
The internal channels are used for:
- sending inputs from client to server
- sending pings between client/server to estimate RTT
- world replication
You would have to define your own channel for your own messages (for example if you need to send a message reliably)
(which you would use usingClientConnection::send_message<C: Channel, M: Message>(message))
ah okay that makes sense, haven't come across the custom channel being used in the examples yet
yes, I don't really use messages much in the examples, but that's definitely something you would need!
absolutely π thanks again
I don't see how to do the inverse, where does a client receive on the same channel?
There are bevy events for all network-related events: https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/client/events.rs#L67
So you just have to listen for the MessageEvent, like so: https://github.com/cBournhonesque/lightyear/blob/main/examples/simple_box/src/client.rs#L128
ah, even easier than I expected!
The transition from renet is interesting, parts like this are abstracted so I don't have to deal w/ the connection at all
You mean that it's abstracted in lightyear or in renet?
in lightyear, which means I get to cut out all of the renet message serialization systems π
I think in renet you can only send raw-bytes, yes. Here since we have the protocol enums defined ahead of time everything can be typed
since I was already converting them to events, I can just handle the lightyear events directly now
How do you guys handle position updates in your projects? Do you use Transform as part of your protocol structs/bundles or do you use an abstraction like PlayerPosition and then a system to manually change the position on your client side?
You should be able to use both; in this example i'm using Transform directly, and in other examples I'm using PlayerPosition
In my case I'm using rapier2d so I'm planning to apply the player inputs to the velocity component instead of modifying their transform/position directly.
Progress! I've got client and server talking w/ lightyear now instead of renet. I'm getting a lot of client ignored packet: invalid packet: # already received errors during map load but it may have to do with how I'm tracking how much of the map has been transferred.
although my Rust-Fu may not be strong enough to implement everything needed on Velocity to make this work.
I can implement the LerpFn w/o issue but I'm not sure how to impl Message for an external type here.
I guess this begs the question if I should be running physics on the client or just let the server run it and replicate transforms to the client. ( I don't really need physics, I'm only using it for collisions right now, but I'd imagine this will come up again )
Hm i'm not entirely sure but I wouldn't be too worried about it. I have replay protection, so you get warnings about duplicate packets. During disconnection I send multiple duplicate packets to make sure the disconnect packets are received
Itβs quite a lot of errors and happens when I trigger a map download, to the point that it seems to affect server fps. Iβll take a closer look at my systems and see if I can reduce or eliminate it.
Is there a way to know what kind of packet is triggering the error?
My map transfer systems are quite crude. Iβm fairly certain I just spam βsend it to meβ until I get all the tiles. π
A Message just needs to implement Named (name of the struct) and a LightyearMapEntities (if there are any entities mapped inside the struct).
You can see some examples of implementations on external types here: https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/utils/bevy_xpbd_2d.rs#L102
If you're doing client-prediction (so that inputs aren't delayed), you probably need to run physics on the client as well (so that the client can simulate physics in a similar way to the server, to avoid rollbacks)
Do you have a link to your code? I'm actually not sure why you would get so many already received errors. It's related to the Replay Protection (https://github.com/mas-bandwidth/netcode/blob/main/STANDARD.md#replay-protection) but i'm not sure what can trigger it
It has to do with my map chunk requests. On the client I was just asking for chunks every frame until I have them all. If I put that request on a timer, so as not to request more than needed, I donβt get the error
Code is in a private repo for now. Iβve got a lot of cleanup to do yet
I think you're able to implement Named here because the trait is defined in your crate. I get an error stating that external traits can't be implemented on external types.
I am able to use a wrapper struct, like PlayerVelocity(pub Velocity) but then I have to keep the players actual Velocity in sync with it somehow.
Oh you're right, that's unfortunate... I have to think of a way to make these work for external components.
I'm pretty new to Rust so unfortunately I don't think I can be of much help, beyond testing things out.
Just thinking out loud:
- A short-term solution would be to implement
Messagefor the rapier_2d types (like I what I did for xpbd), but that's not ideal. - Another solution would be to use reflection, but i'm worried about the performance hit + it would take a long time. Then I could do a runtime check in the type registry if the type needs to implement
MapEntities - Another solution is that the
ComponentProtocolenum is implemented in your crate, so maybe I can add the behaviour on theComponentProtocoldirectly; it's a bit less clear but it might circumvent the orphan rule
Option 1 does seem kind of counter to the goals of the crate - being able to simple add a Replicate component and then all registered components in the protocol "just work" is pretty nice.
That said, physics components may be unique enough that lightyear may only need to support a few of them.
Unrelated, in the book I see this:
The way that it works in lightyear is that for each replicated entity from the server, the client can choose to spawn 2 entities:
a Confirmed entity that simply replicates the server updates for that entity a Predicted entity that is updated instantly on the client, and is then corrected by the server updates
Does this mean the client should insert the confirmed or predicted component to the entity? I'm not sure how to relate a predicted transform to my client side entity.
So basically, in the Replicate component: https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/shared/replication/components.rs#L28
you can specify the prediction_target and interpolation_target, which is the list of clients for which you'll do prediction or interpolation.
Usually prediction_target = the client who controls the entity, and interpolation_target = the other clients.
Then when the entity gets replicated, the original replicated entity will have the Confirmed component added, and a copy of the entity will be created which lives in a different timeline. That copy will have either the Predicted or Interpolated component added. Then there's a syncing process where updates from the network (which get applied to the Confirmed entity) are replicated/synced to the Predicted or Interpolated entity.
All of this is handled for you, the only thing you need to do is specify the prediction_target or interpolation_target
I guess the part I'm missing is what links a server entity to a client entity?
When a player connects, I spawn an entity on the server (bare minimum identifiers etc.) and in turn spawn an entity on the client (rendering components, etc.). How does lightyear know which client entity should get the predicted components?
Lightyear maintains an internal map on the client that maps from the server entity to the client entity
hmm okay, I'll have to get the inspector working so I can see whats going on. I seem to have a disconnect somewhere that I can't wrap my head around.
I think my "predicted" entity is moving around but the actual visible entity is not
this probably means that your inputs are being sent to the client's Predicted entity, but are not being sent to the server; or the server isn't moving the entities based on your inputs
I see both client and server hitting the shared movement system w/ the inputs
Just to confirm:
- the inputs sent to the server are applied to your entities
- the inputs received in the client (via the InputEvent) are applied to the Predicted entity
- the visible entity (rendering components, etc.) are only applied to the Predicted/Interpolated entity (not the Confirmed entity)
the visible entity (rendering components, etc.) are only applied to the Predicted/Interpolated entity (not the Confirmed entity)
maybe? I spawn the client entity after I get a connect event back
what I mean is that you should only render the Predicted entity
I see in the inspector now, I'm actually rendering a third that has no relation to the predicted or confirmed. π€¦ββοΈ
I assume I need to spawn the client entity based on some replicated component instead of on its own after a connect event?
So the flow could be: ClientConnect event on server -> spawn entity on server -> client receives InsertComponentEvent, get the confirmed entity id -> access the predicted entity via the Confirmed component -> add the rendering components on the predicted entity
Or (simpler): ClientConnect event on server -> spawn entity on server -> on client, listen for Query<Entity, (Added<Predicted>, With<MarkerComponent>)> and add your rendering components
You don't need to spawn the client entity, lightyear spawns it for you. (both the Confirmed and Predicted entities). That's what the automatic world replication does π
yea okay I totally goofed on this part lol
in the second example, the MarkerComponent should be some replicated component, right?
otherwise the client ent spawned by lightyear won't have it
yes exactly
okay, its coming together now
For example it's PlayerId in the examples
π
also, for now I'll just keep a separate ReplicatedVelocity(Velocity) component in my protocol and just copy it over to the physics owned Velocity just before applying inputs. Seems to work well enough for now.
Note that bevy already has a Name component, perhaps it's enough to require Name?
Oh but that's trait vs component, maybe not relevant. nvm
I think I found a solution for the external types
Bevy has the Reflect trait that adds basically a name method on the entity.
Maybe I can require that all messages are Reflect, to get name for free?
(and i think I can remove the requirement that messages have #[derive(Message)], so it would be the same number of derives)
I'm still kind of hesistant, because Reflect generates a lot of code, and I only really need the name() currently
The bonus is that most bevy ecosystem types will already be Reflect
Then again, it seems reasonable that you would own the types that you send over the wire yourself
Maybe you can auto-impl Named for any reflected type
I think it's reasonable to want to include external types in your protocol; for example types defined in external libraries (audio, physics, etc.)
well Named is already generated by Reflect
I meant that the user can then choose whether to use Reflect or to impl Named themselves
Yes I think I can achieve something like that
For enemy and NPC entities, does it make sense to give them a ClientId and if so, should I use the Local variant? Does the variant matter?
The Local variant is only used for host-server mode to simulate an 'external' client when in reality the client is in the same machines.
The ClientId is used to identify actual machines connected to the server, not entities
@peak nymph but even if I made messages not require MapEntities, the rapier types don't seem to implement Serialize/Deserialize, so you would still need make a new type no?
Looks like at least the one Iβm using does https://docs.rs/rapier2d/latest/rapier2d/dynamics/struct.RigidBodyVelocity.html
The velocities of this rigid-body.
Itβs just behind a feature flag
Oh cool
I have a PR ready: https://github.com/cBournhonesque/lightyear/pull/225
that will allow you to use any type that is Serialize + Deserialize + Clone as a message
trying out the main branch this morning but not getting message/events on the server, will have to dig in a bit more
are you trying in host-server mode?
I don't think I hooked local client -> server messages in host-server mode
were you able to use Velocity directly in your protocol thouhg?
I'm trying separate client and server right now, let me try host-server mode.
I was able to use Velocity direcly in the protocol, yes - looks much cleaner now!
I meant that host-server mode will fail for sure; but separate client/server should work
ah okay
ah you need to call start() on ServerConnections now
to have more control over when the server starts listening
k let me try that
oh looks like I already have that, I copied the init system from some example:
pub(crate) fn init(mut connections: ResMut<ServerConnections>) {
for connection in &mut connections.servers {
let _ = connection.start().inspect_err(|e| {
error!("Failed to start server: {:?}", e);
});
}
}
pub(crate) fn init(
mut connections: ResMut<ServerConnections>,
) {
connections.start().expect("Failed to start server");
)
You don't get any events, or only MessageEvents are missing?
ah i think i know what it is; some of my io refactor might have broken stuff
are you using WebTransport?
if so, I just pushed a fix in main
using UDP at the moment
weird, the examples work
oh, I'm seeing client connection failed. connection request timed out now
in both separate and listen-server mode, fwiw
curiously, I don't see errors out of either side during
client.connect() or connection.start()
yes I need to improve the error-handling, let me look take a look at in 1hr
I just pushed; does it work now?
connection request timeout with udp?
the server has started correctly?
yes udp and as far as I can tell
does that happen for the first player connecting, or for the second?
i see the same thing for a second player connecting actually
sorry about all that btw
no worries, sorry I'm not too responsive atm. trying to finish out the work week π
let me try another client
same lol
same thing on a second client
oh actually it works for me, I was just using the same client id twice
I'm not sure if I can help much without saying code, since the examples work for me
yea its totally possible I'm doing something wrong
anything I can do to check the server is starting up properly?
I'm not seeing it listening on UDP 5001, but that could also be PEBKAC
yeah there's no log for udp
maybe try with websockets?
there's no need for certificates
same issue on websocket
But you see the logs that say that Starting server websocket task right?
Are you on the latest commit?
2024-04-05T20:39:42.808697Z INFO lightyear::transport::websocket::server: Starting server websocket task
looks like latest
2024-04-05T20:41:07.004687Z INFO lightyear::transport::websocket::client_native: WebSocket handshake has been successfully completed
2024-04-05T20:41:07.004715Z INFO lightyear::connection::netcode::client: client connecting to server 127.0.0.1:5002 [1/1]
...
2024-04-05T20:41:29.838507Z INFO lightyear::connection::netcode::client: client connect failed. connection request timed out
and it was working before?
yea this much was at least
that's strange, maybe take a look at the past commits on the examples: https://github.com/cBournhonesque/lightyear/commits/main/examples/simple_box
to see if i changed anything, but I don't see anything standing out
going to try on a windows machine for another data point
you can also try enabling trace or debug logs (but there might be a lot)
I assume I'd get compiler errors if I didn't have this satisfied right?
https://github.com/cBournhonesque/lightyear/commit/f5c106518ea3262f0746da9d592b253a054a96a3#diff-77fd3852817f67574d3783baae49c11537d6b38e5286714bde2751977641bd21R29-R30
hmm, doesn't compile on windows right now:
```error[E0432]: unresolved import std::os::fd
--> C:\Users\sqwee.cargo\git\checkouts\lightyear-2cfb5e6660946fe3\c59f2b1\lightyear\src\transport\websocket\client_native.rs:2:14
|
2 | use std::os::fd::AsRawFd;
| ^^ could not find fd in os
For more information about this error, try rustc --explain E0432.```
not sure if I tried it before on 0.13
yea compiles and connects okay on this machine on my older commit (lightyear 0.13)
anyway I have to run, really appreciate all your help so far! I may have more time to mess w/ this tomorrow
Just popping in here to say I'm also having issues with clients timing out when trying to connect. I'm using commit c59f2b11 so I could get the leafwing input fix. Client's were able to connect on 0.13, but not on this commit. Everything is setup to use UDP and I have the xpbd_2d and leafwing features enabled. SharedConfig's mode is set to separate. The server appears to start correctly and listen on the specified address. The client also appears to try connecting to the correct address.
If I tell the client to connect to an address that the server is not running at then the client spams "An existing connection was forcibly closed by the remote host". If I enter the correct address, that error is not logged, but the client will timeout instead.
If I find anything else out I'll let you all know here. Really loving lightyear btw
The latest commit that allows my clients to connect is 05835d2f.
0167cde1 breaks it.
Found the issue. 0167cde1 introduces the is_listening field in ServerConnections, which will only get set if you use ServerConnections::start(). That field is used in some new run conditions in the server. Previous versions of the examples or cheatbook showed iterating through the servers, and starting each one manually.
Iβm calling start on them all though, or am I missing something else?
Oh yeah, thanks @oak girder you're completely right!
@peak nymph you have to call
pub(crate) fn init(
mut connections: ResMut<ServerConnections>,
) {
connections.start().expect("Failed to start server");
)
calling start() on each ServerConnection individually doesn't work, it has to be on the ServerConnections (note the 's').
Calling starts sets a is_listening flag here that enables some run conditions to not run any networking systems if the server is not started.
Okay I see now, confusing that each connection also has a public start() π
Sorry i'm moving a lot of things around in main (the io, MapEntities, etc.). There are a few rough edges
You're right, i might remove access to the servers field entirely
okay yea we're back in business now w/ that change, thanks to you both!
seeing lots of this now:
2024-04-06T14:08:32.427265Z ERROR lightyear::shared::replication::send: Received an update message-id ack but we know the corresponding group id```
the predicted and confirmed entities desync almost immediately and get worse the further from spawn I move
Predicted/confirmed desync is usually some kind of logic error; inputs not getting applied to both predicted on client-side, and entity on server-side
It's interesting that you see Received an update message-id ack but we know the corresponding group id, that error should be hard to reach. I've never seen it myself
I'm definitely doing something wrong when the player dies on the server, thats when the errors start
Basically when we send an update for a given entity group:
- the sender keeps track of the MessageId for that group, and stores the info in a
HashMap<MessageId, ReplicationGroupId> - the sender periodically receives ACKs from the receiver.
- when it knows that the receiver ACKed a MessageId, it finds the corresponding
ReplicationGroupby doingmap.remove(message_id)
So i guess you could get this error if the receiver sends multiple ACKs for the same MessageId. (i'm not entirely sure why this would happen. Maybe if a message needs to get re-sent multiple times? For example in a reliable channel?)
You have a lot of these error messages, or just a couple of them when you despawn the entity on the server? I might not have an example where I simply despawn an entity on the server; maybe this error log is expected.
Also did you add packet loss?
You have a lot of these error messages, or just a couple of them when you despawn the entity on the server?
I have a lot of them. Its probably because when the player dies right now I just move their transform directly back to spawn, but I don't replicate the transforms
I'll have to rework that stuff
I'm not too worried by Received an update message-id ack but we know the corresponding group id honestly; even if it happens I don't think it's a big deal.
The annoying part for you is more that predicted and confirmed are not in sync
going to try replicating the transform too, I feel like relying on changes to velocity to behave the same in 2 physics plugins is going to be flaky anyway
After connecting a client (A) to the server, I am creating a player entity on the server, which contains a Transform component (which is part of the protocol and in sync = full and with lerp = TransformLinearInterpolation) which is then sent as interpolated entity to all other clients (X,Y,Z) (spare the client that just connected). What I can see on clients X,Y,Z is that my Interpolated entity only gets its Transform component added, after I provide some movement on client A. Is this intentional? Without any movement on client A immediately after connecting, I would have expected the clients X,Y,Z to receive the initial value for Transform as it was created for the player entity on the server.
I know that we need two states two be able to interpolate between, but I would expect that the first state transmitted would contain an initial value
My query for interpolated entities looks like Query<(Entity, &Transform), Added<Interpolated>> which will not have entries due to Interpolated being added before a Transform is present
Yes that's something I noticed; I actually made the change intentionally, but it might not have been the correct decision. See those 2 issues: 99 and 160
Basically what I noticed was that if the send_interval rate is low, then the interpolation was behaving weirdly at the beginning (while waiting to have 2 component updates). Basically the entity would be 'fixed' for send_interval and then start moving. Which is strange for example for interpolated bullets: the bullet was frozen for send_inteval and then start moving
But I agree that the current version is not a good solution. Maybe I can make 'spawn the component on interpolated immediately' the default behaviour, with a possibility to opt out.
(or option 2) there may be another solution: for example after the time for 2 server updates has passed, and we haven't received a server update for that entity, we just spawn the component on the Interpolated entity).
I like option 2 actually
Probably would be nice to have it configurable
With a large send_interval waiting for two updates may still feel sluggish
I don't have too much time today, but i'll look into it monday or tuesday
β€οΈ
@orchid urchin https://github.com/cBournhonesque/lightyear/pull/229 this should fix the problem!
You will need to be on the latest commit though; and i've made a few changes recently
But it would still require a query like Query<(Entity, &Transform, &Interpolated), Added<Transform>>, instead of Query<(Entity, &Transform), Added<Interpolated>>, because the Transform component is added not during initial creation of the Entity but like 1.3 * send_interval later, right?
In other words: The Interpolated is attached to the Entity since the first moment the client receives that Entity (analogous to Predicted), while the Transform will be added to the Entity some delta time later on
I would like to have the initial value of Transform added to the Entity at the same time the Interpolated gets added - if this is possible and if it makes sense
That way, the only difference in handling Predicted spawns and Interpolated spawns would be the inner type of the Added query filter
Hm I see what you mean; the initial value will currently be stored in the InterpolateStatus component (see this), which is used to compute the final (interpolated) component value.
I'll try to think of an ergonomic way to configure this behaviour
I think the current behaviour is more correct and should maybe remain the default.
Should it be overridable per entity? per entity-component pair? or on the entire app?
What are you doing with a query like Query<(Entity, &Transform), Added<Interpolated>> ?
I guess another option would be to use Query<(Entity, Option<&Transform>), Added<Interpolated>>
I am just learning how to use lightyear :). For every player connected to the server, I spawn a PbrBundle (Cuboid) on client side while having its Transform as part of the network protocol. If I understood correctly, the cube that I as a player control, gets added the Predicted component while the cubes of the other connected players get the Interpolated component added. So when the query Query<(Entity, &Transform), Added<Predicted>> is not empty, I know that I need to spawn my PbrBundle on the client.
This worked well, and then I tried the same Query, but with Added<Interpolated> to spawn the PbrBundles for the remote players. Then I found out, that the Transform was not present on those entities.
I see; for now I would suggest relying on a marker component (for example IsCube) to identify the correct entity rather than Transform (mostly because in the future you might have other replicated entities that have Transform).
The IsCube marker component can be replicated with sync_mode=once since it only needs to be replicated once at the start. It will get added immediately to the Interpolated entity.
Sorry if this is not an ideal solution yet :/
(and then your queries would be Query<Entity, (Added<Predicted>, With<IsCube>)> )
But then I would still miss the initial location (let's assume, the server picks a random Vec3 for a player's location at the time the player connects to the server)?
you mean that you need the Transform to be able to add a PbrBundle? (i'm not familiar with render components)
yup
That is how I do it right now for Interpolated spawns:
pub fn handle_interpolated_spawn(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
interpolated: Query<(Entity, &Transform, &Interpolated), Added<Transform>>,
) {
for (entity, transform, _) in interpolated.iter() {
info!("Handling interpolated spawn of entity {entity:?}");
commands.entity(entity).insert(PbrBundle {
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
material: materials.add(Color::rgb(0.0, 0.0, 1.0)),
transform: *transform,
..default()
});
}
}
I think this might be the simplest solution for now; i'll open an issue to track your problem, I can definitely see how in this situation it is not ideal that the Transform component only gets added later
It is a slight variation of https://github.com/cBournhonesque/lightyear/blob/main/examples/simple_box/src/client.rs#L160
I wonder why the method in your example works, because it also assumes that a PlayerColor is present on the entity at the time Added<Interpolated> evaluates to true
For some reason, your PlayerColor seems to be attached to the entity while my Transform is not
Yes it's what I mentioned above: https://github.com/cBournhonesque/lightyear/blob/main/examples/simple_box/src/protocol.rs#L75
the components that don't have sync-mode = "full" are synced immediately from Confirmed to Predicted/Interpolated.
- full = run prediction/interpolation
- simple = copy every update to the interpolated/predicted entity (but otherwise no prediction/interpolation)
- once = copy the value of the component once from confirmed to predicted/interpolated
I see π I wonder if an additional state, that is a combination of once and full, is feasible? Run prediction/interpolation but synchronize initial state first
I will have a look at InterpolateStatus
(The whole idea of using a Transform instead of an abstract type like PlayerPosition was to let Bevy manage the position update itself, making a separate system that takes the coordinates out of PlayerPosition and puts it into Transform superfluous)
Maybe! Your current system works right? Even though the query Query<(Entity, &Transform, &Interpolated), Added<Transform>>, is not ideal
The issue is that the behaviour would be tied to the component itself; you might want to have that behaviour on one entity (the player) but not another (for example bullets)
I did not test your latest changes yet. With my code so far, my local player sees the remote players only after the remote players made some inputs
But I am in the process of rethinking. Sending an initial spawn position with sync_mode=once sounds much better to me
That could be a good solution too! In any case I opened an issue to not forget about this: https://github.com/cBournhonesque/lightyear/issues/230
If it does not pose a problem to anyone, just do nothing π I just wanted to share my observation and not to confuse anyone π
no I think it's very valid! It's certainly unintuitive that interpolation components are not added on the interpolation entity at the same time as the Interpolation component!
β€οΈ
Booyah! I love that! It works
I realized the leaf wing input example does what Iβm trying to do, with regards to replicating a velocity and using that to update movement. Iβve tried implementing as close as I can with rapier but my predicted and confirmed entities still desync over time. Is there some periodic correction that can be done to transforms without full sync?
There is rollback applied all the time, you can try to turn on the logs with the filter lightyear::client::prediction::rollback=debug
It looks like even with rollback, the few ticks where the client is predicting ahead (using the client inputs + the physics rules) are not close to what the server will have.
- do you have prediction in
mode=full? - they shouldn't completely desync over time, but instead maybe 'flicker' a bit as the rollback happen
Is prediction mode separate from replication mode?
What do you mean by replication-mode?
The "sync-mode" (https://docs.rs/lightyear/latest/lightyear/client/components/enum.ComponentSyncMode.html) is what I call "how do we replicate components from the Confirmed to the Predicted/Interpolated entity".
You need to define it correctly on your protocol (for example here)
Sorry yes thatβs what Iβm referring to, I have it set to full.
I havenβt set a corrector but Iβd be okay with see instant corrections for now. I just donβt seem to get any, which has me thinking that something about my rapier setup doesnβt play nice with rollbacks, Iβm not sure what exactly would be rolled back and how it would be handled by physics.
Try turning on the logs; normally you should see a bunch 'rollback' logs.
Whenever we receive a server update (on the Confirmed entity), we compare it with the history of the Predicted entity (to find what the predicted entity was doing at that tick). If it doesn't match, we rollback and replay the last few inputs on the client
when that happens, does the whole entity get rolled back or just the client inputs?
I mean, does lightyear somehow know to adjust the entities transform? Otherwise just undoing a velocity change won't be enough to change trajectory
Only the components that are marked "full" in the protocol get rolled back
okay that makes sense
So the two entities don't actually desync if I replicate transforms but movement is super laggy due to rollbacks. I see constant Transform component mismatches in the rollback logs.
I guess my physics fixed step is messed up and the tick always mismatches
Maybe; rollbacks shouldn't be noticeable at all.
Are you doing collisions with other players? that is not predictable out of the box
I've disabled the collisions for now
Maybe; rollbacks shouldn't be noticeable at all.
even if they're happening every few ticks?
I see them every 2-5 ticks when moving around
I guess they are noticeable if your physics logic is not exactly the same on client vs server.
Is your physics logic running in the FixedUpdate schedule? Is it running after inputs are handled?
You might be missing an ordering between some of your systems
ugh, yea you're right - I was trying to explicitely order the physics systems in a new fixed set but I also needed this: https://docs.rs/bevy_rapier2d/latest/bevy_rapier2d/plugin/struct.RapierPhysicsPlugin.html#method.in_fixed_schedule
A plugin responsible for setting up a full Rapier physics simulation pipeline and resources.
much smoother now lol!
aha cool! Yes debugging those issues is quite painful, I went through all these with my leafwing_inputs example already :p
painful indeed, I was half tempted to just dumb down the client, remove physics all together but I'm glad its working now - thanks yet again π
Do you have thoughts on this error when compiling on windows?
error[E0432]: unresolved import `std::os::fd`
--> C:\Users\sqwee\.cargo\git\checkouts\lightyear-2cfb5e6660946fe3\c59f2b1\lightyear\src\transport\websocket\client_native.rs:2:14
|
2 | use std::os::fd::AsRawFd;
| ^^ could not find `fd` in `os`
For more information about this error, try `rustc --explain E0432`.
I see some old issues that suggest this may not be available on Windows?
What is the intention behind that line? I mean, it is quite a handful: https://github.com/cBournhonesque/lightyear/blob/main/examples/simple_box/src/client.rs#L115
Are you doing collisions with other players? that is not predictable out of the box
Collisions work well in the leafwing input example, did you have to do something special there?
it loks like that api is not available on windows, yes. Also it seems unused in the code? let me try to remove it
Removed it, thanks for the report
I wanted to be able to test if I changed the sync_mode from "full" to "simple" in the protocol. In that case you don't want the client inputs to affect the Predicted entity, because you simply copy the server updates from the Confirmed to the Predicted entity.
Currently the only way to get the sync mode for a component is to use this <Components as SyncMetadata<PlayerPosition>>::mode() function, but yes it's very unergonomic, I will have to change it. I didn't really prioritize that because in most cases users shouldn't be using that function
no, thank you! really appreciate all the help β€οΈ
For collisions to other players to work, the local client is predicting both its controlled player but also the other players. (i.e. the other players are predicted, not interpolated). (otherwise collisions wouldn't work because interpolated players are in the past, and predicted players are in the future). Similarly the ball is also predicted. This is what rocketleague does (gdc video)
So it adds a bit complexity when adding the Replicate component to hook up things correctly. (you need to receive the inputs from other players as well to be able to predict them) But also the main problem is that you now have big mispredictions, because you might predict another player to move to the left, which is not actually the case since they started turning right (but you haven't received this input yet).
To mitigate those mispredictions:
- you can add some input-delay: i.e. if you press Left at tick T, it will actually only take effect at tick T+d. When d is bigger than the RTT, there are 0 mispredictions anymore. There is an option to add input-delay in the ClientConfig
- you can add visual correction: when there is a rollback, instead of snapping back to the corrected position, you visually interpolate over a few ticks between the current position and the corrected position
The leafwing example has both of these (only visual correction is enabled), but as you can see it's not perfectly smooth! My hope is to be able to make it smoother
Thanks for the explanation! I had a feeling it had to do w/ the prediction of other clients and I just now saw that predict_all was set in the settings.
I'll try to get this working too - do server controlled entities/npc's need the same treatment?
intuitively I would think so
but in that case there wouldn't fewer mispredictions (no twitchy inputs), so thinga should look near perfect
@pine cape: The tutorial link on the GitHub page results in a 404
I am having a look at https://github.com/cBournhonesque/lightyear/blob/main/book/src/tutorial/title.md where there is still the Message trait derived in the protocol, but I think this is outdated. I will create a PR for it
Yep, I'm trying to upload a new version of the tutorial but I ended up messing up the deployment
Thanks for the PR!
I made a demo where we have more control over the state of client connection; you can connect/disconnect the client at runtime (which was not possible before, the only way to disconnect the client was to shutdown the app).
It's a small step towards total runtime control over the connection, where we can potentially change from separate server to host server mode at runtime
@pine cape the tutorial is definitely clearer now, but i am stuck at the init functions for both client and server. In the tutorial it says to use client.connect() and sever.start() but for both i get this error: error[E0599]: no method named `connect` found for struct `bevy::prelude::ResMut<'_, ClientConnection>` in the current scope
Are you using the latest commit, or 0.13?
lightyear 0.13.0 it says in my cargo,toml
Hm i'm a bit surprised, the function definitely exists: https://docs.rs/lightyear/latest/lightyear/prelude/client/trait.NetClient.html#tymethod.connect
Did you import the prelude? use lightyear::prelude::client::*;
API documentation for the Rust NetClient trait in crate lightyear.
I will add the imports in the tutorial as well
alright the client.connect now exists but the server.start still doesn't exist
Ah, yes ServerConnections is in main but not part of 0.13.0, sorry.
It's best to directly refer to the example in the 0.13.0 tag: here
fn init(mut connections: ResMut<ServerConnections>) {
for connection in &mut connections.servers {
let _ = connection.start().inspect_err(|e| {
error!("Failed to start server: {:?}", e);
});
}
}
alright tnx for your help
@pine cape one more question haha in the tutorial at the io_config for the client the variable addr is used in the TransportConfig::UdpSocket but addr isn't declaired before hand i think, wich addr is used there?
It is the address of your server; when setting up your server's IoConfig you will have to specify a local SocketAddr to use; probably SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), YOUR_PORT)
(Ipv4Addr::UNSPECIFIED means 'local machine', essentially; but you will have to decide on a port)
alright tnx
im doing something wrong because when i run the init code ```fn init(mut client: ResMut<ClientConnection>) {
client.connect().expect("failed to connect to server");
println!("{:?}", client.is_connected());
}``` doesn't give back the failed to connect to server but the client.is_connected is false
No that should be right; client.connect() just initiates the connection process; then the client server need to exchange a bunch of packets (to authentify, etc.) before being connected)
ah oke, so i need to wait a bit before they are connected?
Yes exactly
is there a way to know when they are connected?
Yes you can just listen for the ConnectEvent: https://github.com/cBournhonesque/lightyear/blob/0.13.0/examples/simple_box/src/client.rs#L50
ah right oke tnx
how long does the exchange of a bunch of packages take usually because it is just not connecting for me
like 1 second? Do you have some code I can look at?
i'll make it into a github rep so i can share it but i think i'm gonna continue on Monday. Thanks for all your help so far i defenetly understand it alot better now
Sounds good! happy to hear that
In systems shared between client and server, is there a reasonable way to know if its running on the client or server?
I'm trying to work out a shared "apply damage" system but on the client I would need to adjust the predicted component where on the server I'd be adjusting the confirmed? component. ( I think the server entity always has the confirmed marker? )
Or should I just keep these as separate systems and call a common function w/ the correct health component?
It would probably be something like:
Or<With<Predicted>, With<Replicate>>
(predicted matches on the client, replicate on the server).
Is this for host-server mode?
If you're running 2 separate apps; you could also use NetworkIdentity to check if you're running on the client or server
You can use this as a SystemParam to identify whether youβre running on the client or the server
Yes, this Or condition is what i'm using in my examples; it works both in separate and host-server mode
excellent, I'll give it a shot
@pine cape https://github.com/SofianLute/bevy_online_lightyear/blob/main/src/client.rs this is the code i have so far
This works for me: https://github.com/SofianLute/bevy_online_lightyear/pull/1/files
The main change is to switch from
let server_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 5001);
to
let server_addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 5001);
in the client.rs file
nice it works now thanks for your help
@pine cape Hello, I am trying to get the steam transport to work but I am getting this error:
2024-04-14T17:24:01.488454Z INFO lightyear::client::networking: calling connect on netclient
thread 'Compute Task Pool (17)' panicked at C:\Users\User\.cargo\registry\src\index.crates.io-6f17d22bba15001f\steamworks-0.11.0\src\lib.rs:465:13:
assertion failed: !sockets.is_null()
This corresponds to this line:
https://github.com/Noxime/steamworks-rs/blob/master/src/lib.rs#L465
I don't know what the issue could be. I am on the latest 0.11.0 steamworks which is what lightyear is using
Which causes the NetClient.connect to fail since the library is not initialized: https://github.com/cBournhonesque/lightyear/blob/53a9279091579ae3a12e7aa003f731d9467042eb/lightyear/src/connection/steam/client.rs#L88
@bronze elm
Are you trying on 0.13.0 or on main? I tried on main and I got the same error, I'm investigating.
(unfortunately it's hard for me to test this because I can't install Steam on my work laptop)
Yes this is on main. I've been trying to dive into this. I had my friend try as well, but he is getting a debug_assert_eq error which fails earlier than that.
I have tried installing the 158A steam SDK and setting it via STEAM_SDK_LOCATION etc. but the dll byte for byte is identical to the one in the cargo manifest.
So I don't think it's a DLL issue, it may be a regression in steamworks-rs, but what's strange is lightyear is already pegged at 0.11.0 so when you tested this back in February it would have already been at 158A
the reason I mention this is in case there was either a too new, or too old DLL being referenced on my system, but I have also verified that e.g. simple_box.exe is referencing the steamapi64.dll in the 'out' directory
Yeah the debug_assert_eq is due to this: https://github.com/Noxime/steamworks-rs/pull/168
@keen crown have you seen this error before?
Sorry running on low energy, what exactly is the error?
It works on 0.13.0, so the regression has been introduced recently.
Could it because I recreate the netclient upon connecting? https://github.com/cBournhonesque/lightyear/pull/234/files#diff-a10713a06b75acd68435b38d79f9f5012f524e6745e0a6d0283e6a5d59154294R91
The error is getting
thread 'Compute Task Pool (17)' panicked at C:\Users\User\.cargo\registry\src\index.crates.io-6f17d22bba15001f\steamworks-0.11.0\src\lib.rs:465:13:
assertion failed: !sockets.is_null()
on the client side when running
self.client
.networking_sockets()
.connect_by_ip_address(self.config.server_addr, vec![])
.context("failed to create connection")?,
The docs do mention that init_app should only be created once per program: https://github.com/Noxime/steamworks-rs/blob/master/src/lib.rs#L236
Yeah you should only initalize it once thats correct and then you need to shutdown it when you are done, this is normally the case by starting the game and by closing it
How do you shut it down?
When I recreate the object it should call the drop fn for the previous Client, no?
Well yeah it does, the problem is steam hooks or watch into the process (and spawned childs) and as long as its running its not really shutdowning, so as long as the app is not existing should the steam client be keep alive.
Actually I was creating a new client before dropping the old one; if I drop the previous client, I get another error: the client gets disconnected immediately with Reason: RemoteBadCert; weird
This only happens if I drop the Client (which should shutdown cleanly: https://docs.rs/steamworks/latest/src/steamworks/lib.rs.html#504) and then recreate the Client. If i just create the Client once it works
Source of the Rust file src/lib.rs.
I think this might be how steam works. The expectation is that steam is bound to the process until it ends. I think that was what Trust was saying above
Also another instance of someone having the same issue in another library: https://github.com/rlabrecque/Steamworks.NET/issues/187#issuecomment-323700244
I wonder if you can init multiple times and itβs actually the shutdown call thatβs the problem
Thanks for the link; I also tried to initialize the steamworks client only once via OnceCell (https://github.com/cBournhonesque/lightyear/pull/242/files) but I still get disconnected with reason: RemoteBadCert)
It doesn't seem to be related to dropping/recreating the client; it works if I connect the client at app initialization, but it doesn't work if I connect it later on via a button click
Ok I found something:
- if I don't connect immediately, the first connection attempt will get disconnected from the server with
RemoteBadCertbut future connection attemps will work. This is not ideal but at least it sort-of works. I could add an automatic retry for the first connection attempt ..
What do you think about this?
https://partner.steamgames.com/doc/api/ISteamNetworkingSockets#InitAuthentication
This caught my eye when skimming the docs. Maybe the bad cert error is related to the authentication not being ready before trying to connect?
I pushed a change that should make things more-or-less work (the first connection fails but after this it works).
Thanks for the link! I added it to the issue here.
Unfortunately I won't be able to make progress on this until april 23 because I won't have access to any computers with steam. (but maybe you can try InitAuth call?)
@pine cape hello, im try the last part of the tutorial but i get stuk at the firts thing #[protocol(sync(mode="once"))] gives me this error error: cannot find attribute `protocol` in this scope --> src\protocol.rs:54:7 | 54 | #[protocol(sync(mode="once"))] | ^^^^^^^^ | note: `protocol` is imported here, but it is a function, not an attribute --> src\protocol.rs:52:1 | 52 | #[component_protocol(protocol = "MyProtocol")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the attribute macro `component_protocol` (in Nightly builds, run with -Z macro-backtrace for more info)
Ah this part of the tutorial has been updated for the latest commit on the main branch, I should only deploy new tutorial changes when I release a new version.
I think your best bet is to look directly at the example on the 0.13 tag: https://github.com/cBournhonesque/lightyear/blob/0.13.0/examples/simple_box/src/protocol.rs#L73
is it possible to spawn a pbr mesh to act as player instead of continuously drawing a gizmo?
Yes it is possible;
- spawn a player entity on the server, with a marker component (maybe
PlayerId) - on the client, listen for a client getting replicated via
EventReader<ComponentInsertEvent<PlayerId>>, the event contains theEntity - add your pbr mesh to the entity
do i have to include that marker component in the component_protocol?
also how does that work with Confirmed vs. Predicted? Will the mesh be on both entities?
Everything that is sent through the network needs to be part of the protocol, so yes
You will have to add the mesh on the correct entity yourself. It will probably be on the Predicted or Interpolated entity, the Confirmed entity usually doesnβt need to be rendered (since it wouldn't be rendered smoothly).
So you could do something like
Query<Entity, (With<PlayerId>, Added<Predicted>)> to find the correct entity
Trying to wrap my head around client authoritative vs. client prepredicted and drew up some sequence diagrams. If you get a moment please let me know if they're accurate and useful. I'm still a bit unsure on the ShouldBePredicted component and if it should be included somewhere.
https://github.com/cBournhonesque/lightyear/pull/250
Thanks! Yes I think I definitely to add more diagrams! Thanks I will take a look. All docs-related PRs are super helpful
ShouldBePredicted is an internal component to signal to the client that they should spawn a separate Predicted entity, I don't think it should appear in the books
Iβll try to collect some notes from my renet->lightyear exercise and see if they fit anywhere.
I think maybe the references in the book should be changed from ShouldBePredicted to PrePredicted? It looks like the latter has the client_entity field now.
Yes, pre-prediction works by adding the PrePredicted component; ShouldBePredicted is purely internal now; I think some parts of the book are outdated
Okay and the expectation when using PrePredicted is the client entity has its Replicate component removed after the initial replication?
Yes exactly! (it's done automatically for you: https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/client/prediction/pre_prediction.rs#L170)
I'm not fast enough in the inspector to see this happen, but my client entity loses its Replicate component but I don't get a confirmed entity. The server doesn't seem to get my client->server replicant
On the server side the Confirmed Component doesn't get added. (Confirmed only gets added when an entity needs to be predicted/interpolated; it's not added if there's no prediction/interpolation. But maybe I should add it for any entity that was created via Replication)?
Maybe check via EntitySpawnEvent or ComponentInsertEvent to check that the entity was replicated correctly from client -> server?
Not seeing it just yet, but I'm on main and getting spammed by this line today:
https://github.com/cBournhonesque/lightyear/commit/02d1fcc9a68057972f38fc5e446753bac15f580b#diff-0d5ad0d3872d0cbbaa3fc6699a6cf634d8ef0560029174bcd6d06b65387d207fR356
This reads like it should be expected, did you mean to leave it at warn level?
I reverted that to debug locally and don't see any entityspawn or component insert events for the client->server replication call. will keep poking around
Ah no, I was just turning it on for debugging; maybe I should switch all development to a separate branch develop so the main branch stays clean
I don't mind being on the bleeding edge, just wanted to confirm my understanding of that func was correct
ie - messages should be present
Hm, a few other possible reasons:
- prediction in general only works when the client is synced (its tick is synced with the server tick); maybe you're spawning the entity too early before client is synced? (i'm not even sure this is a problem because replication would probably start only after syncing is done, but might be a reason)
- maybe the
replicate_cleansystem is running too early somehow? (this also shouldn't be the case because it's running after all replication systems: link) But maybe you can log when it happens if you want to check
messages should be present when something is replicated, yes
Oh I know
I'll probably change those settings because it's not intuitive; but you need to enable client->server replication in the ReplicationConfig: https://github.com/cBournhonesque/lightyear/blob/main/examples/client_replication/src/main.rs#L260
okay that did it, just need to find the source of constant rollbacks now π
your examples work so well, I probably should have just scrapped my project and started fresh from your example lol
Ah i thought you had fixed the rollbacks before :p
hah yea I've been chasing my tail several times - had to switch over to leafwing inputs because the default setup doesn't appear to support multiple inputs in the same frame
I expect I need to tweak my input delay settings to get the inputs in the right tick
Input delay is mostly to help reduce the number of mis-predictions when you try to predict other players' inputs; but if you're not interacting with other players (where you cannot predict their inputs correctly) it shouldn't matter much. You already should have close to 0 mispredictions
(it's also useful to reduce the number of rollback frames if you have performance constraints)
yea I've disabled collisions between players, other players and enemies for now to simplify my life
still get constant mismatches on Transform components right now though, playing with settings..
It's probably not a setting issue; you might not be running the logic correctly on the Predicted entity on the client, or you might have an ordering issue. Did you make sure that physics runs after your systems that handle inputs?
input handling is handed off to the leafwing plugins now but applying those inputs is definitely done before physics runs
The desync rollback behavior is intermittent somehow. I would still like to understand why I get so many of those βreceived ack for already ackedβ errors to know if itβs somehow affecting fps
@pine cape there seem to be issue with WT selfsigned when using Firefox (check gh, i pinged u in the issue).
would require some testing. Ill check if i can get it to run on FF tomorrow.
this seems related to the frequency of your packet updates. I bet it doesn't happen if you set the send_interval to a lower frequency.
I would also like to understand why it happens, I also noticed this myself. I will file a ticket: https://github.com/cBournhonesque/lightyear/issues/254
thanks! taking a look
@pine cape do you maybe have an example of replicating a resource cause i'm not sure on how to do it
Is this different than the error here ? I tried setting my client send interval and server send interval to the same 40ms and still get this error.
I don't have a published example since I just added Resource replication recently.
But basically:
- add
ReplicateResource<R>to your ComponentProtocol, like this - use the
commands.replicate_resource::<R>(replicate), wherereplicateis theReplicatestruct that defines who you will replicate the resource to. See here - you can use
commands.stop_replicate_resource::<R>()to stop the replication - any updates to the Resource (including removal) will be replicated.
Note that this will clone the Resource, so it's not appropriate if the resource is expensive to clone
Another option is to just replicate the Resource via a Message manually.
It's a big of a footgun right now, but I think input replication doesn't work if client_send_interval is not 0.
(because the client is exactly RTT/2 in the future compared to the server, so if you send inputs infrequently, some client inputs might not arrive on time in the server). This might be why you get infrequent rollbacks! It shouldn't be a problem to set it to 0 because there's very little data to send from client to server.
Try setting client_send_interval to 0ms, and server_send_interval to 100ms, and the error logs should disappear (they still should be investigated)
Hi, I've been setting up lightyear for my game but ran into some structural issues.
I'd like to have a menu, where a player can start a lobby. This is where a server is spun up, which should be fine to do in a separate thread, like in some of your examples. But I'm having trouble figuring out how to handle the client as I can't find anywhere in your examples how to, at runtime, to set the IP and port of the server to connect to.
So, player 1 starts a lobby (creating a server (on another thread)). He also starts connects to his own server as a client.
Player 2 connects to the lobby by typing in an IP and port in some UI fields and clicking a connect button.
Is there a way to set up something like this?
Hi, my current focus is exactly this: making the client/server config more runtime-configurable (see this issue: https://github.com/cBournhonesque/lightyear/issues/183). I actually have a WIP PR that will add an example called 'lobby' that does more-or-less what you describe:
- there will be a lobby that users can join (the lobby UI uses a dedicated server)
- in the lobby you can select who will be 'host' (either the dedicated server or one of the clients)
- then when someone clicks start, the game starts for everyone using the selected configuration
I believe that what you describe is already more-or-less do-able; if you check out the simple_box example you will see that I added a 'Connect/Disconnect' button. While the client is disconnected, you can update the ClientConfig (for example to change the ip/port of the server to connect to), and when you click 'Connect' the new ClientConfig will be re-loaded and used.
I'm also rather new to Bevy so I'm trying to figure out how to update the ClientConfig. It is added via the PluginConfig, which again is added to the ClientPlugin, so how exactly do I update the ClientConfig?
I don't have access to the app besides from the main function, where the program starts (and functions that can be called therefrom).
Is it like a resource that can be updated at a later point?
Hmmm... After digging some more, I fing this in the ClientPlugin:
.insert_resource(netclient)
.insert_resource(config.client_config.clone())
.insert_resource(ConnectionManager::<P>::new(
config.protocol.channel_registry(),
config.client_config.packet,
config.client_config.sync,
config.client_config.ping,
config.client_config.prediction.input_delay_ticks,
))
Do I need to overwrite all of these 3 or just the client_config resource?
It's a resource, so you can just update it via a system like so:
fn update_config(mut config: ResMut<ClientConfig>) {
config.net = my_new_net_config;
}
and no, this is the only thing that you should update.
On the next connection attempt, I will reload any internal resources that are needed (ConnectionManager, etc.)
Thanks!
I'll give this a try and also look into your PR on how you've set that up.
Do I need the PR to make something like this work or is that just streamlining the process?
I actually started out w/ settings close to that:
client_send_interval: Duration::default(),
server_send_interval: Duration::from_millis(40),
Tried setting the server interval to 100 but still see frequent rollbacks and the ack err server side.
I think it should work on main; the PR is not ready yet so I don't think it will be helpful currently. It might be ready in about 2 weeks? I'm on holiday rn so I can't make much progress.
hm that's strange, the ack err completely disappear for me when I set it to 100
Yea I donβt doubt Iβve got some other issues going on. Iβm tempted to try xpbd instead of rapier to see if it helps.
Maybe restarting from my physics example would help, yes
What would be considered expensive to clone? Is a hashmap expensive? using ReplicateResource<R> gives me an error that a similarly named struct exists named ReflectResource
It should be fine
Maybe you're missing an import; did you import the prelude, or ReplicateResource?
Still having issues after switching to bevy_xpbd - I'm curious if there are bandwith constraints on the internal replication channels? I've got a ton of transforms for terrain tiles for example, is it possible I'm trying to replicate too much?
Well, even if I don't add terrain I've still got super low FPS and rollbacks so its probably something else...will keep digging.
Kinda seems like a client with low FPS will cause server FPS to tank too, as soon as I disconnect my misbehaving client the server FPS recovers.
Profiling the server doesnβt point at anything obvious in my code. The hottest function is in bincode preparing a write buffer
Iβm dumb, I donβt have replicate components on the terrain so thatβs not it. Still curious about channel limits though
There are no channel limits! Maybe enable some logs on the write/receive side to check that everything is replicated correctly, at the right tick?
Do you have open-source code? I could help take a look
Itβs in a private repo right now because itβs a hot mess. I could send you a collab invite if you want
Sure, maybe I can help out
Kind of a weird one. I left two clients running connected via the same server with WebTransport. Came back 30-60 mins later and I had no errors on either client, or the server, but the updates stopped syncing. Oddly, when I closed one of them I saw the entities despawn on the other, so it was still connected I guess
Was this in wasm?
It is possible that there was a problem due to tick wrapping, I thought I had fixed those entirely but maybe not
Maybe clientside-only desync then
how would that only happen in wasm
π€
@stiff dome i like your approach of decoupling io from the tick loop entirely. It does sound reasonable that raf could be responsible for this
@pine cape dont u use i32s for ticks?
wrapping shouldnt happen then
π€
No, it happened on a windowed app
no I use u16 for ticks, and I've had wrapping issues in the past, i'll test again
Maybe varuint32 then?
it's possible to make it work with u16
What is the purpose of LeafwingInput2 in the Protocol?
With leafwing you can have multiple different UserActions, if that's the case, you need to register each of them in your protocol (with LeafwingInput1, LeafwingInput2, etc.)
I didn't add higher numbers because no one expressed the need for more, but I can do it
Hmm ok. Would that be used for something like running with Shift and W for forward? Or am I missing the point of having multiple?
A UserAction is generally attached to an entity to represent the 'actions' that an entity can take.
So you could have different 'types' of actions that could be done by different entities:
- for example a set of UserActions for the player entity (movement, crouch, jump, etc.)
- then a set of 'admin' UserActions (stop, pause, save, etc.)
- or maybe a different set of UserActions if you control a different entity (for example you control a different character and now your actions are [talk, run] instead of [movement, crouch, jump]
(eachUserActioncan have its own set of keybindings)
Here is the documentation on leafwing: https://docs.rs/leafwing-input-manager/latest/leafwing_input_manager/trait.Actionlike.html
It's basically a set of 'actions' that an entity can perform
Ok gotcha. Thanks for the help
I can give you access to my private project where it's implemented
So you can have a reference
That would be awesome :)
@bronze elm the bug with long-lived connections should be fixed now
that's great, thanks, I'll check it out
@pine cape how would you keep track of player scores and show those scores on the clients screens?
Create a resource or component on the server that contains all the player scores, and replicate it to other players (with mode='simple')
Hello, I was wondering if you had any suggestions on how to go about diagnosing physics sync issues. I have created a github repo to demonstrate my problem:
https://github.com/panjeet/networked_cube_test
I am using bevy-tnua which is basically a character controller for bevy_xpbd and in this example I have spawned a ground plane and a player. For the interpolated players they look great, but for the player themselves, I just get crazy jitter, which is confusing because I am running the same physics code on both just like the leafwing example.
In general I add logs showing the status of the Transform component at various schedules (after each FixedUpdate, after Physics, after Last) along with tick information. You can add those logs yourself via systems.
I also log inputs that are sent, and maybe rollback-related logs. For these logs you might need to tweak the log-filters to enable some lightyear internal logs.
I run the game and log both the client/server for a few seconds, then go through the logs to see any discrepancy. The biggest sources of discrepancy are incorrect system ordering and incorrect input handling.
(it's a bit easier to run through the logs when running server/clients separately)
Also check if it works well without prediciton/interpolation I guess; or in host-server mode
Thanks @bronze elm for the report; I have a PR which is my debugging setup: https://github.com/panjeet/networked_cube_test/pull/1
I also included some thoughts about what might be happening
My current hunch is that there are some components that impact the entity's Position (such as Tnua components) that aren't present in the ComponentProtocol; this means that they cannot be rolled-back, and the rollback doesn't give you exactly the same state on the client and server. This is just an idea as I'm not familiar with tnua; maybe tnua components are purely updated from xpbd components so it's not a problem
Also it looks like the tnua systems use GlobalTransform internally instead of Position? maybe that's causing the issue. i.e. we replicate Position, but tnua uses GlobalTransform which still isn't updated since it's not part of the protocol, so the tnua systems give you a different position. This might be a bigger issue actually
That was it; I added GlobalTransform to the ComponentProtocol and it seems to work now π (except for Jumps, but that should be easily fixable)
Wow, weird
Thank you so much for looking at that and for the PR. I had some ideas based on your notes, and just made a commit that is a step in the right direction. I ended up replicating just 'Transform' and the LinearVelocity to the player itself (NetworkTarget::Single). Now the floating and jumping are working correctly.
Awesome!
@pine cape im trying to add Transform to my component protocol but it gives this error ```error[E0277]: the trait bound bevy::prelude::Transform: multiplayer::protocol::_::_serde::Serialize is not satisfied
--> src\multiplayer\protocol.rs:28:1
|
28 | #[component_protocol(protocol = "MyProtocol")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait multiplayer::protocol::_::_serde::Serialize is not implemented for bevy::prelude::Transform
...
39 | Transform(Transform),
| --------- required by a bound introduced by this call
|
= help: the following other types implement trait multiplayer::protocol::_::_serde::Serialize:
bool
char
isize
i8
i16
i32
i64
i128
and 488 others
note: required by a bound in serialize_newtype_variant
--> C:\Users\Sofian.cargo\registry\src\index.crates.io-6f17d22bba15001f\serde-1.0.198\src\ser\mod.rs:936:21
|
928 | fn serialize_newtype_variant<T>(
| ------------------------- required by a bound in this associated function
...
936 | T: ?Sized + Serialize;
| ^^^^^^^^^ required by this bound in Serializer::serialize_newtype_variant
You have to enable the 'serialize' feature on bevy so that Transform implements the serialize trait
okay and how do i do that? π
You would have to add it to the Cargo.toml: https://github.com/panjeet/networked_cube_test/pull/1/files#diff-2e9d962a08321605940b5a657135052fbcef87b5e360662bb527c96d9a615542R29
alright tnx it works now π
@dull lion @jade ember I got my lobby example working!
- The
ClientConnectionandServerConnectionconfigs can be changed at will when the client/server is disconnected, they will be reloaded at runtime at the next connection attempt - Here you have a dedicated server which maintains a list of lobbies; when a client clicks on the
Start gamebutton, the game is hosted either by the dedicated server (each lobby has its ownRoom) or by one of the players which will act as host temporarily during the duration of the game
Did anything change recently with starting connections? Pulled main this morning and canβt connect to the server again, kinda similar behavior to when connections.start() was changed
I have been staying on the lobby example branch for this reason. There are a lot of improvements to the connection setup
cool, I'll try that branch
Yes I noticed as well that the main branch was broken; I'm merging the changes from cb/lobby-example soon
There are some breaking changes though (on how to start/stop the connections)
okay sounds good, no worries! Just wanted to make sure I wasn't going insane π
merged!
Working on a server browser similar to Rust (the game) using the latest dynamic netconfig changes
Very cool!
Nice! I took inspiration from Rust for my procgen map, unfortunately thatβs about all I have working okayish at the moment lol
Oh that is so cool!
Sorry i would Love to Support a bit more in this area, but the Next few weeks are really stressful for me because of Final projects in my Apprentice ship.
After that i have a Lot more time and would Love to Support if there is anything left to do for steam Networking
no worries, focus on your final projects π
Awesome π
very cool
@pine cape Is it true that the lobby aspects aren't part of lightyear yet if you run cargo add lightyear ? Because i tried using them but they don't exist it said
Yep, the lobby example is part of an unreleased version (in the main branch), not part of 0.13.0!
oke good to know, then i'll patiently wait for the release π
you can also use the branch directly by specifying it in your Cargo.toml: https://github.com/panjeet/networked_cube_test/blob/master/Cargo.toml#L39
alright tnx
getting a lot of this error on main today
2024-04-25T18:04:24.423526Z ERROR lightyear::server::networking: Error updating netcode server: could not update server
Caused by:
0: IO error: unable to receive message from client: receiving on a closed channel
1: unable to receive message from client: receiving on a closed channel```
no client is running or attempting to connect
So you just start the server and you see this error?
yep
which IO are you using? webtransport?
udp and websocket
yea let me double check that the port isn't taken by something else
hmm same thing on a different port
ill keep digging..
seems to be this: https://github.com/cBournhonesque/lightyear/blob/main/lightyear/src/transport/websocket/server.rs#L224-L224
but i don't get how you would receive something if there are no clients
by the way, would you find it useful if I got rid of the Protocol/MessageProtocol/ComponentProtocol enum and instead you can register each type to replicate directly on the app?
(something like app.register_message::<M>())
so that you don't have to put the entire protocol in the same spot
Personally I don't mind (actually prefer) having my whole protocol in one place but what you're suggesting would still allow for that with a plugin doing all the registrations