#lightyear

1 messages Β· Page 2 of 1

pine cape
#

i'll try both

floral vector
#

@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();
    }
  }
}

elder dome
#

@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

floral vector
#

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?

elder dome
#

you can do it somewhere else if you want

#

you can make another system

#
.add_systems(Update, handle_disconnected_player_cleanup)
floral vector
#

just want to

  • implement this
  • not make code super ugly(but this is non-mandatory)
elder dome
#

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"

floral vector
#

oh, i see

elder dome
#

Or just throw another event to cleanup that entity

floral vector
#

then should add the method but not system

elder dome
#

or add a component to that entity to indicate that it will be cleaned up (not a replicated component)

floral vector
#

now i think about function, which will be call before id removed. Other looks complex

pine cape
#

What doesn't work with the current logic?
Is the entity not despawned for other clients?

elder dome
#

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

pine cape
#

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

floral vector
elder dome
#

ah

floral vector
#

so you can f.e. connect, move somewhere, disconnect(close the window)... and second client has the ... ghost/garbage

elder dome
#

hmm I wonder if lightyear will delete children on client if parent is destroyed on server

floral vector
# pine cape What is the "player gltf"? Is it a component attached to the entity? or some kin...
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);
        }
      }
    }
  }
  
elder dome
pine cape
#

Gltf seems to be a component

#

So deleting the entity should work

pine cape
elder dome
#

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

pine cape
#

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)

elder dome
#

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
}
elder dome
#

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

floral vector
#

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

elder dome
#

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

floral vector
#

@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) ?

pine cape
#

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?

floral vector
#

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

pine cape
#

I cna't say without more information. You should run with bevy_inspector_egui and check which entities/components are still present

floral vector
#

Do you mean, generally there is expectation that the gltf will be removed after disconnect event happens ?@pine cape

pine cape
#

If you're adding the gltf as components to the entity, yes

floral vector
#

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

elder dome
#

hmm @floral vector what if you dont despawn it?

#

i think it just gets despawned before it can be replicated with x=25 position

floral vector
#

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());
    }
  }
elder dome
#

you tried not despawning anything @floral vector ?

floral vector
#

no. it sounds like wrong idea

#

do you mean just comment block below my new code?

elder dome
#

comment out this:

commands.entity(entity).despawn();
floral vector
#

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

pine cape
#

I would advise to use the inspector to see if the entity/component got despawned correctly

floral vector
#

@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();
    // }
  }
}

elder dome
#

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?

pine cape
#

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

floral vector
#

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

pine cape
#

can you please explain what the gtlf is... (i.e. is it a component? ) or send a screenshot with the inspector open

floral vector
# pine cape can you please explain what the gtlf is... (i.e. is it a component? ) or send a...

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

pine cape
#

can you link your repo

floral vector
#

i can not say what is it component or something

#

yes

elder dome
#

i think it would have some hierarchy

pine cape
#

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.

elder dome
#

you should use the inspector
here i explained how to get it, its simple

pine cape
#

looks like it's a special component that spawns children

#

In which case this is not supported right now

pine cape
#

I need to add a system to copy Replicate to all child entities

elder dome
#

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"

pine cape
#

what is a prefab

elder dome
#

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

hushed rivet
#

ya you need to despawn_recursive gltf scenes

elder dome
#

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

pine cape
#

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

elder dome
#

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

pine cape
#

Well it seems like it's also possible right now; since the Gtlf is kind of like a prefab. It spawns a whole hierarchy

elder dome
#

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

pine cape
#

Ok so 2 options:

  • when I receive a replicated 'despawn' , I use despawn_recursive instead of despawn. (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
floral vector
# elder dome I don't think unity netcode for entities supports that kind of thing

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

elder dome
#

hmm, actually i think despawn_recursive is fine
when client receives a despawn

elder dome
floral vector
#

yes

elder dome
#

then its more difficult

hushed rivet
#

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

pine cape
#

@floral vector just run with --inspector, I already include the inspector

floral vector
#

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

pine cape
floral vector
#

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

hushed rivet
#

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

floral vector
#

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

floral vector
#

looks like there is non-zero chance we will see 0.7.1 soon 🍡

pine cape
#

So is the gtlf despawned instantly or not? it should be almost instant (like 0.3 sec max)

floral vector
#

@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

pine cape
#

ah yeah lightyear takes a while to register the disconnect. (the timeout for disconnections is configurable)

floral vector
#

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

pine cape
#

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

elder dome
#

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)

pine cape
#

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

elder dome
#

maybe try changing the replication groups

#

?

pine cape
#

so the player's rollback (whenever a Confirmed update arrives) would affect the bullets

#

no i have it working now

elder dome
#

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

pine cape
#

I've merged the branch; so you should be able to use prespawning with the latest commit! @elder dome

elder dome
#

All right i'll switch tomorrow I think

#

You are fast, nice work

floral vector
#

@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

pine cape
elder dome
#

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

floral vector
#

@elder dome any video?

#

if the project is not a secret

elder dome
#

ah, didnt realize it would be so low in frames

floral vector
#

little rounds is gravitation?

elder dome
#

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

pine cape
#

Great to hear that prespawning works well for you!

#

The misprediction system is pretty smart, you only apply it on the predicted entity?

floral vector
# elder dome i'll be trying to build something interesting after this, now I have everything ...

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...

β–Ά Play video
elder dome
lunar robin
#

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!

pine cape
#

Awesome, super happy to hear that! Let me know if you need any help πŸ™‚

lunar robin
#

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.

pine cape
floral vector
#

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

hushed rivet
#

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

pine cape
#

can you elaborate? what should I change?

hushed rivet
#

impl PredictionCommandsExt for EntityCommands<'_, '_, '_> { -> impl PredictionCommandsExt for EntityCommands<'_> {

kindred garden
hushed rivet
#

any noise reduction in bevy_ecs is well wanted =p @kindred garden

kindred garden
#

@sleek crane deserves the credit here :p

hushed rivet
#

@young prawn and @sleek crane seem to do plenty of changes I like

#

many others ofc

pine cape
#

Ah I see; I guess I'll only make that change once bevy-0.13.0 comes out

floral vector
#

@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

pine cape
#

It looks like just cannot find the remote IP?

floral vector
#

@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

floral vector
#

@pine cape other machines works as expected. it was network or admin settings

hushed rivet
#

probably just a firewall, with that said, lightyear should handle a failed route gracefully

floral vector
#

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

pine cape
lunar robin
#

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.

wet basin
#

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

pine cape
#

Ah it might be because it uses the version 0.7.0 of lightyear-macros, as @lunar robin mentioned above. Re-publishing now

wet basin
#

Ah thanks! That was it

pine cape
#

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)

wet basin
#

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

pine cape
#

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)

onyx salmon
#

@pine cape if you decide to move transport library into a separate crate - ping me, I will be happy to test it.

pine cape
#

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]

GitHub

Networking library for the Bevy game engine. Contribute to cBournhonesque/lightyear development by creating an account on GitHub.

GitHub

Networking library for the Bevy game engine. Contribute to cBournhonesque/lightyear development by creating an account on GitHub.

onyx salmon
pine cape
#

what is deseriliaze seed? do you have a link?

onyx salmon
#

In case of Reflect it allows to pass app type registry.

pine cape
onyx salmon
#

But bitcode can't do streaming serialization, so streaming deserialization is not quite useful, unfortunately.

#

Which does support it.

pine cape
#

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

onyx salmon
#

When I serialize components

#

Not only components, everything.

#

Instead of creating a struct, we stream data into buffer directly.

pine cape
#

What do you mean by streaming serialization? Being able to serialize components into a buffer not in one go?

onyx salmon
#

It's like 4x times faster according to our benchmarks.

pine cape
onyx salmon
#

But it will create an intermediate buffer, right?

#

Since bincode doesn't have a streaming API

#

In bitcode every call returns a new Vec.

onyx salmon
#

Typo, sorry :)

pine cape
#

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]

onyx salmon
onyx salmon
#

Also you don't use bytes, right?

#

bytes, the crate

pine cape
#

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?

onyx salmon
#

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.

pine cape
#

ah so you do use channels, not just the raw recv/send api

onyx salmon
#

Yes :) NetServer is a lover level abstraction?

#

Renet have RenetServer with a similar API, but also with channels

pine cape
#

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
onyx salmon
#

Got it. Looks like I need only channels one.

pine cape
#

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 πŸ™‚

onyx salmon
#

Got it, so it structured a bit differently.

floral vector
#

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 πŸƒ πŸ’¨

timid grove
#

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 πŸ™‚

GitHub

Contribute to Ablu/mana_combat_demo development by creating an account on GitHub.

GitHub

Contribute to Ablu/mana_combat_demo development by creating an account on GitHub.

pine cape
#

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:

GitHub

Networking library for the Bevy game engine. Contribute to cBournhonesque/lightyear development by creating an account on GitHub.

GitHub

Networking library for the Bevy game engine. Contribute to cBournhonesque/lightyear development by creating an account on GitHub.

timid grove
timid grove
#

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 πŸ™‚

pine cape
#

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!

GitHub

Contribute to Ablu/mana_combat_demo development by creating an account on GitHub.

#

Don't hesitate if you have any questions/feedback

timid grove
timid grove
# pine cape Don't hesitate if you have any questions/feedback

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?

pine cape
#

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?

timid grove
timid grove
pine cape
#

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)

timid grove
#

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 πŸ™‚

pine cape
#

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)

GitHub

Networking library for the Bevy game engine. Contribute to cBournhonesque/lightyear development by creating an account on GitHub.

timid grove
lunar robin
#

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`
pine cape
#

Hm let me take a look, maybe the Eq bound is not actually required.

pine cape
#

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

GitHub

A straightforward stateful input manager for the Bevy game engine. - Leafwing-Studios/leafwing-input-manager

GitHub

A straightforward stateful input manager for the Bevy game engine. - Leafwing-Studios/leafwing-input-manager

#

I will remove the Eq bound for the non-leafwing inputs though, they don't seem required

lunar robin
#

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.

pine cape
#

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

lunar robin
#

Shall do. I can't imagine I'm the first to try this kind of input with that library.

lunar robin
lunar robin
pine cape
#

thanks, let me take a look!

lunar robin
#

I see your PR result now has the bevy_asset import issue that I ran into.

pine cape
lunar robin
#

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

pine cape
#

Yes! strange that it doesn't give any compile errors but just panics like this at runtime; it's pretty error-prone

lunar robin
#

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

pine cape
#

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

pine cape
pine cape
#

@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

pine cape
#

ah i can't test because my work laptop apparently blocks steam πŸ˜…

unkempt vigil
pine cape
#

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?

unkempt vigil
#

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

pine cape
#

ah sure, will let you know, thanks!

unkempt vigil
#

Just because steam is annoying you need 2 actual steam accounts running on separate computers to actually test it πŸ™ƒ

pine cape
#

really? wow

keen crown
#

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 πŸ˜„

pine cape
#

Ah ok, I paid the steam fee so i can create a test app otherwise

pine cape
#

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

timid grove
#

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...

pine cape
#

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"?

timid grove
unkempt vigil
#

@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.

pine cape
#

yep that's something I want to support, there is an issue for this actually. I can look into that

unkempt vigil
#

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 ?

pine cape
#

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

unkempt vigil
# pine cape what do you mean by "a player runs the client & server" btw? that the player is ...

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.

pine cape
pine cape
# unkempt vigil It's a common technique to have the game server support being run as a dedicated...

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!

GitHub

Networking library for the Bevy game engine. Contribute to cBournhonesque/lightyear development by creating an account on GitHub.

unkempt vigil
# pine cape That's interesting, it would be cool to have an example that showcased this! So ...

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.

pine cape
#

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

unkempt vigil
# pine cape Thanks! Yeah i'll look into the multiple networking support! I'm not too familia...

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
stuck dock
#

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?

pine cape
stuck dock
#

am i supposed to run that?

pine cape
#

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

GitHub

Networking library for the Bevy game engine. Contribute to cBournhonesque/lightyear development by creating an account on GitHub.

GitHub

Networking library for the Bevy game engine. Contribute to cBournhonesque/lightyear development by creating an account on GitHub.

#

(the certificate has been added to git 4 days ago)

stuck dock
#

ahhh

stuck dock
pine cape
#

oh i see; let me know if you have errors still!

pine cape
#

@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!

GitHub

Context
This PR allows the user to create a Server that uses multiple ServerConnections at the same time.
(for context, a Transport is a trait to transmit raw bytes (UDP, WebTransport, WebSockets, ...

stiff quiver
#

@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

pine cape
#

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

stiff quiver
pine cape
#

i don't really like env variables for that either

stiff quiver
#

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?

pine cape
#

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

stiff quiver
stiff quiver
#

thats another important thing to consider

#

Sure neither does a cli but still

pine cape
#

include_str works, thanks

lunar robin
#

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).

pine cape
#

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

stuck dock
#

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?

pine cape
#

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
stuck dock
#

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

stuck dock
#

this is the simplebox example

pine cape
#

By default the examples are using webtransport

#

Hm i use rustrover and don't have any errors. Does the example run correctly?

stuck dock
#

The entire repo has a total of 254 warnings and errors from rust analyzer

pine cape
#

Probably rust analyzer is parsing the code with the feature 'leafwing' enabled (the feature is disabled for this example)

pine cape
#

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

stiff quiver
#

huh i got a ghost ping

#

πŸ‘€

pine cape
#

@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.

GitHub

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..)

GitHub

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)

jade ember
# pine cape (maybe <@151215593553395721> you also have an idea? this is how I initialize my ...

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?

pine cape
#

sorry, I forgot to push a change; how about now?

jade ember
#

yep that works, altough rust analyzer still complains about a lot in the vendor directory lol

jade ember
#

ah wait, i found the settings.ron

pine cape
keen crown
pine cape
keen crown
pine cape
keen crown
#

yes for everything network related and of course other callbacks

brittle cloak
jade ember
jade ember
#

got it working

pine cape
#

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

GitHub

Hi, I was wondering how I could access the networking_sockets functions from the Server side. there is no networking_sockets() function on the Server struct there seems to be no way of creating a C...

jade ember
#

I'll look into that a bit more later, not familiar enough with the code base of steamworks-rs

pine cape
#

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

jade ember
#

i've done p2p before already with steam invites and so on, so i could possibly help here

pine cape
#

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

jade ember
#

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 :)

pine cape
#

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)

jade ember
#

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

pine cape
#

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!

jade ember
#

i think that would be really cool!
I might also look into it this weekend

dull lion
#

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?

pine cape
#

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.

dull lion
#

Yeah I think "full-mesh" p2p is a niche use case

stuck dock
brittle cloak
#

And requires a non server authoritative structure

stuck dock
jade ember
# pine cape Not necessarily, it's just that I didn't really think about other networking top...

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

pine cape
#

Yes πŸ™‚ try any example with cargo run -- listen-server
The client and server run on the same machine, but in different threads.

jade ember
#

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"

pine cape
#

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

jade ember
#

i thought adding plugins at runtime isn't supported by bevy?

#

or did i miss something?

pine cape
#

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

GitHub

Networking library for the Bevy game engine. Contribute to cBournhonesque/lightyear development by creating an account on GitHub.

GitHub

Networking library for the Bevy game engine. Contribute to cBournhonesque/lightyear development by creating an account on GitHub.

jade ember
#

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 :)

pine cape
dull lion
#

@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

pine cape
#

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

jade ember
# pine cape I opened an issue for this: https://github.com/cBournhonesque/lightyear/issues/1...

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.

dull lion
#

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

pine cape
#

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

dull lion
#

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

pine cape
#

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.

dull lion
#

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?)

onyx salmon
stuck dock
dull lion
dull lion
onyx salmon
dull lion
#

Hmm the host-client should still have to predict/interpolate, since it does not have up-to-date knowledge of other clients' states, right?

onyx salmon
dull lion
#

Right, but it can run this logic without having a networking client running

onyx salmon
#

Yes.

onyx salmon
# dull lion Right, but it can run this logic without having a networking client running

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 :)

dull lion
#

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

pine cape
#

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 Interpolated for 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?
dull lion
pine cape
#

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

dull lion
#

you still have latency from other players though, and your host-client should probably try to run at the same tick as other clients?

pine cape
#

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

onyx salmon
pine cape
#

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

dull lion
#

Okay, thanks for clarifying

dull lion
onyx salmon
pine cape
#

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 . During FixedUpdate it reads ServerInputEvent and 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 ServerInputEvents instead 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.

dull lion
#

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.

pine cape
#

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

pine cape
#

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 component Local to 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::Local client 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/Interpolated would have to be adapted to work directly on the server entities
pine cape
#

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?

dull lion
#

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

pine cape
#

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

dull lion
#

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)

pine cape
#

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 Confirmed entity. (an alternative could be to add the Confirmed component directly on the server entity) I think we need this for:
    • interest management
    • it is possible that the Confirmed entity 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 Interpolate component directly on the Confirmed entity. (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 Predicted component directly on the Confirmed entity, 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
dull lion
#

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?

pine cape
#

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

dull lion
#

Right. I think it's fine to copy though!

#

Might make the separation cleaner

pine cape
#

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

dull lion
#

very excited about these changes, I'll be trying them out as soon as they land

pine cape
#

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

dull lion
#

yeah I suppose this is only an issue when you do the copying entities strategy

pine cape
#

Do you know of other games where client/server are in the same world? I think you mentionned minecraft before

dull lion
#

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

pine cape
#

I guess it's nice to have the option

dull lion
#

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

pine cape
#

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)

#

Maybe i just need to rearchitect the examples?

dull lion
#

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).

pine cape
#

yeah, 0 client code at all

dull lion
#

I guess the main complication is that you want to run input systems on the server

pine cape
#
  • thinking about how the normal dedicated-server client code (with Predicted/etc.) has to be adapted for this
dull lion
#

yeah

pine cape
#

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

dull lion
#

sure!

onyx salmon
pine cape
#

I mean that since you don't add the renet-client plugin, it's similar to disable the replicon client

onyx salmon
pine cape
#

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)

onyx salmon
pine cape
#

@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

jade ember
#

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

dull lion
#

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>

pine cape
#

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!

pine cape
#

@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

dull lion
dull lion
#

I added a few review comments

peak nymph
#

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.

dull lion
peak nymph
#

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

pine cape
peak nymph
#

okay great, does some portion of the crate handle the build target selecting which transport to use or do I have to implement that?

pine cape
#

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:

peak nymph
#

ah okay that helps, thanks!

#

really nice work so far on the crate btw, I look forward to trying it out!

pine cape
#

Awesome, let me know how it goes πŸ™‚

peak nymph
#

will do, I'm sure I'll be back with more questions once I get in to it πŸ˜„

pine cape
#

@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)
dull lion
#

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

pine cape
#

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

dull lion
#

Maybe if the premade filters are clever enough, you don't even need to add the Predicted/Interpolated marker components

pine cape
#

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?

stiff quiver
pallid vector
pine cape
#

it does seem related to std::time, thanks

pine cape
#

Released a new version with all the fixes/changes: #crates message

bitter garnet
#

I've got a question regarding the steam integration, do you support the steam servers in anonymous mode? IE as dedicated servers?

pine cape
#

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

bitter garnet
#

All the previous steam integrations I've seen are all peer to peer, and require a steam client to be logged in and running

bitter garnet
pine cape
bitter garnet
#

Yup, that's what I was afraid of.

orchid urchin
#

Thank you, @pine cape, for your work on lightyear! ❀️

snow musk
#

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 :-)

visual sand
#

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?

pine cape
#

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

peak nymph
#

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?

pine cape
#

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 using ClientConnection::send_message<C: Channel, M: Message>(message) )
peak nymph
#

ah okay that makes sense, haven't come across the custom channel being used in the examples yet

pine cape
#

yes, I don't really use messages much in the examples, but that's definitely something you would need!

peak nymph
#

absolutely πŸ˜„ thanks again

peak nymph
pine cape
peak nymph
#

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

pine cape
#

You mean that it's abstracted in lightyear or in renet?

peak nymph
#

in lightyear, which means I get to cut out all of the renet message serialization systems πŸ˜„

pine cape
#

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

peak nymph
#

since I was already converting them to events, I can just handle the lightyear events directly now

orchid urchin
#

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?

pine cape
#

You should be able to use both; in this example i'm using Transform directly, and in other examples I'm using PlayerPosition

peak nymph
#

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.

peak nymph
#

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.

peak nymph
#

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 )

pine cape
peak nymph
#

My map transfer systems are quite crude. I’m fairly certain I just spam β€œsend it to me” until I get all the tiles. πŸ˜…

pine cape
pine cape
pine cape
peak nymph
#

Code is in a private repo for now. I’ve got a lot of cleanup to do yet

peak nymph
#

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.

pine cape
#

Oh you're right, that's unfortunate... I have to think of a way to make these work for external components.

peak nymph
#

I'm pretty new to Rust so unfortunately I don't think I can be of much help, beyond testing things out.

pine cape
#

Just thinking out loud:

  • A short-term solution would be to implement Message for 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 ComponentProtocol enum is implemented in your crate, so maybe I can add the behaviour on the ComponentProtocol directly; it's a bit less clear but it might circumvent the orphan rule
peak nymph
#

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.

peak nymph
#

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.

pine cape
#

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

peak nymph
#

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?

pine cape
#

Lightyear maintains an internal map on the client that maps from the server entity to the client entity

peak nymph
#

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

pine cape
#

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

peak nymph
#

I see both client and server hitting the shared movement system w/ the inputs

pine cape
#

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)
peak nymph
#

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

pine cape
#

what I mean is that you should only render the Predicted entity

peak nymph
#

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?

pine cape
#

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 πŸ™‚

peak nymph
#

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

pine cape
#

yes exactly

peak nymph
#

okay, its coming together now

pine cape
#

For example it's PlayerId in the examples

peak nymph
#

thanks again!

#

yep that did it, now we've got movement

pine cape
#

πŸŽ‰

peak nymph
#

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.

dull lion
#

Oh but that's trait vs component, maybe not relevant. nvm

pine cape
#

I think I found a solution for the external types

pine cape
#

(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

dull lion
#

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

pine cape
#

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

dull lion
#

I meant that the user can then choose whether to use Reflect or to impl Named themselves

pine cape
#

Yes I think I can achieve something like that

peak nymph
#

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?

pine cape
#

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

pine cape
#

@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?

peak nymph
#

It’s just behind a feature flag

pine cape
#

Oh cool

peak nymph
#

trying out the main branch this morning but not getting message/events on the server, will have to dig in a bit more

pine cape
#

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?

peak nymph
#

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!

pine cape
#

I meant that host-server mode will fail for sure; but separate client/server should work

peak nymph
#

ah okay

pine cape
#

ah you need to call start() on ServerConnections now

#

to have more control over when the server starts listening

peak nymph
#

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);
        });
    }
}
pine cape
#
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?

peak nymph
#

no events

#

I don't even see the connect events

pine cape
#

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

peak nymph
#

using UDP at the moment

pine cape
#

weird, the examples work

peak nymph
#

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()

pine cape
#

yes I need to improve the error-handling, let me look take a look at in 1hr

pine cape
#

I just pushed; does it work now?

peak nymph
#

let me see

#

still seeing the timeout and no events

pine cape
#

connection request timeout with udp?
the server has started correctly?

peak nymph
#

yes udp and as far as I can tell

pine cape
#

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

peak nymph
#

no worries, sorry I'm not too responsive atm. trying to finish out the work week πŸ˜„

#

let me try another client

pine cape
#

same lol

peak nymph
#

same thing on a second client

pine cape
#

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

peak nymph
#

yea its totally possible I'm doing something wrong

peak nymph
#

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

pine cape
#

yeah there's no log for udp

#

maybe try with websockets?

#

there's no need for certificates

peak nymph
#

same issue on websocket

pine cape
#

But you see the logs that say that Starting server websocket task right?
Are you on the latest commit?

peak nymph
#

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
pine cape
#

and it was working before?

peak nymph
#

yea this much was at least

pine cape
peak nymph
#

going to try on a windows machine for another data point

pine cape
#

you can also try enabling trace or debug logs (but there might be a lot)

peak nymph
#

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

oak girder
#

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

oak girder
#

The latest commit that allows my clients to connect is 05835d2f.
0167cde1 breaks it.

oak girder
peak nymph
pine cape
#

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.

peak nymph
pine cape
#

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

peak nymph
#

okay yea we're back in business now w/ that change, thanks to you both!

peak nymph
#

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

pine cape
#

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

peak nymph
#

I'm definitely doing something wrong when the player dies on the server, thats when the errors start

pine cape
#

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 ReplicationGroup by doing map.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?

peak nymph
#

I'll have to rework that stuff

pine cape
#

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

peak nymph
#

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

orchid urchin
#

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

orchid urchin
#

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

pine cape
#

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

orchid urchin
#

Probably would be nice to have it configurable

#

With a large send_interval waiting for two updates may still feel sluggish

pine cape
#

I don't have too much time today, but i'll look into it monday or tuesday

orchid urchin
#

❀️

pine cape
orchid urchin
#

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

pine cape
#

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>>

orchid urchin
#

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.

pine cape
#

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>)> )

orchid urchin
#

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)?

pine cape
#

you mean that you need the Transform to be able to add a PbrBundle? (i'm not familiar with render components)

orchid urchin
#

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()
        });
    }
}
pine cape
#

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

orchid urchin
#

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

pine cape
#

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
orchid urchin
#

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

orchid urchin
#

(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)

pine cape
#

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)

orchid urchin
#

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

pine cape
orchid urchin
#

If it does not pose a problem to anyone, just do nothing πŸ™‚ I just wanted to share my observation and not to confuse anyone πŸ˜„

pine cape
#

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!

orchid urchin
#

❀️

orchid urchin
peak nymph
#

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?

pine cape
#

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
peak nymph
#

Is prediction mode separate from replication mode?

pine cape
peak nymph
#

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.

pine cape
#

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

peak nymph
#

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

pine cape
#

Only the components that are marked "full" in the protocol get rolled back

peak nymph
#

okay that makes sense

peak nymph
#

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

pine cape
#

Maybe; rollbacks shouldn't be noticeable at all.
Are you doing collisions with other players? that is not predictable out of the box

peak nymph
#

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

pine cape
#

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

peak nymph
#

much smoother now lol!

pine cape
#

aha cool! Yes debugging those issues is quite painful, I went through all these with my leafwing_inputs example already :p

peak nymph
#

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?

orchid urchin
peak nymph
#

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?

pine cape
#

Removed it, thanks for the report

pine cape
# orchid urchin What is the intention behind that line? I mean, it is quite a handful: https://g...

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

peak nymph
pine cape
# peak nymph > Are you doing collisions with other players? that is not predictable out of th...

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

peak nymph
pine cape
#

intuitively I would think so

#

but in that case there wouldn't fewer mispredictions (no twitchy inputs), so thinga should look near perfect

orchid urchin
#

@pine cape: The tutorial link on the GitHub page results in a 404

orchid urchin
pine cape
#

Yep, I'm trying to upload a new version of the tutorial but I ended up messing up the deployment

#

Thanks for the PR!

pine cape
#

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

west garnet
#

@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

pine cape
#

Are you using the latest commit, or 0.13?

west garnet
#

lightyear 0.13.0 it says in my cargo,toml

pine cape
#

I will add the imports in the tutorial as well

west garnet
#

alright the client.connect now exists but the server.start still doesn't exist

pine cape
#

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);
        });
    }
}
west garnet
#

alright tnx for your help

west garnet
#

@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?

pine cape
#

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)

west garnet
#

alright tnx

west garnet
#

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

pine cape
#

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)

west garnet
#

ah oke, so i need to wait a bit before they are connected?

pine cape
#

Yes exactly

west garnet
#

is there a way to know when they are connected?

pine cape
west garnet
#

ah right oke tnx

west garnet
pine cape
#

like 1 second? Do you have some code I can look at?

west garnet
#

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

pine cape
#

Sounds good! happy to hear that

peak nymph
#

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?

pine cape
#

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

peak nymph
#

ah okay, I'm testing w/ separate apps right now

#

the Or condition should work πŸ‘

pine cape
#

Yes, this Or condition is what i'm using in my examples; it works both in separate and host-server mode

peak nymph
#

excellent, I'll give it a shot

west garnet
pine cape
west garnet
bronze elm
#

@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

GitHub

Rust bindings to the SteamWorks SDK. Contribute to Noxime/steamworks-rs development by creating an account on GitHub.

GitHub

Networking library for the Bevy game engine. Contribute to cBournhonesque/lightyear development by creating an account on GitHub.

pine cape
#

@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)

bronze elm
# pine cape <@912493549575082005> Are you trying on 0.13.0 or on main? I tried on `main` an...

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

pine cape
#

@keen crown have you seen this error before?

keen crown
#

Sorry running on low energy, what exactly is the error?

pine cape
#

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")?,
keen crown
#

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

pine cape
#

How do you shut it down?
When I recreate the object it should call the drop fn for the previous Client, no?

keen crown
#

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.

pine cape
#

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

bronze elm
# pine cape This only happens if I `drop` the Client (which should shutdown cleanly: https:/...

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

GitHub

Using the latest Unity SDK 10.0 (and also with the prev. version) I noticed that when I exit play mode from Unity - the SteamAPI is still active. In steam I still see my game as Running and the onl...

pine cape
pine cape
#

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

pine cape
#

Ok I found something:

  • if I don't connect immediately, the first connection attempt will get disconnected from the server with RemoteBadCert but 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 ..
bronze elm
pine cape
#

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?)

west garnet
#

@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)

pine cape
#

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

GitHub

Networking library for the Bevy game engine. Contribute to cBournhonesque/lightyear development by creating an account on GitHub.

west garnet
#

is it possible to spawn a pbr mesh to act as player instead of continuously drawing a gizmo?

pine cape
#

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 the Entity
  • add your pbr mesh to the entity
west garnet
bronze elm
#

also how does that work with Confirmed vs. Predicted? Will the mesh be on both entities?

pine cape
pine cape
peak nymph
#

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

GitHub

For my own education, I figured I'd try to draw out the client replication sequences.
If you find these accurate and helpful, I'd be happy to draw up more for the other topics as I come to ...

pine cape
#

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

peak nymph
peak nymph
pine cape
#

Yes, pre-prediction works by adding the PrePredicted component; ShouldBePredicted is purely internal now; I think some parts of the book are outdated

peak nymph
#

Okay and the expectation when using PrePredicted is the client entity has its Replicate component removed after the initial replication?

pine cape
peak nymph
#

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

pine cape
#

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?

peak nymph
#

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

pine cape
#

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

peak nymph
#

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

pine cape
#

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_clean system 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
pine cape
#

Oh I know

peak nymph
#

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

pine cape
#

Ah i thought you had fixed the rollbacks before :p

peak nymph
#

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

pine cape
#

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)

peak nymph
#

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..

pine cape
#

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?

peak nymph
#

input handling is handed off to the leafwing plugins now but applying those inputs is definitely done before physics runs

peak nymph
#

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

stiff quiver
#

@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.

pine cape
west garnet
#

@pine cape do you maybe have an example of replicating a resource cause i'm not sure on how to do it

peak nymph
pine cape
# west garnet <@263123021336805376> do you maybe have an example of replicating a resource cau...

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), where replicate is the Replicate struct 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.

pine cape
# peak nymph Is this different than the error [here](https://github.com/cBournhonesque/lighty...

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)

wraith seal
#

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?

pine cape
#

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.

wraith seal
#

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?

pine cape
#

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.)

wraith seal
#

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?

peak nymph
pine cape
pine cape
peak nymph
#

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.

pine cape
#

Maybe restarting from my physics example would help, yes

west garnet
pine cape
#

It should be fine

#

Maybe you're missing an import; did you import the prelude, or ReplicateResource?

peak nymph
# pine cape Maybe restarting from my physics example would help, yes

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.

peak nymph
#

Profiling the server doesn’t point at anything obvious in my code. The hottest function is in bincode preparing a write buffer

peak nymph
pine cape
#

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

peak nymph
pine cape
#

Sure, maybe I can help out

bronze elm
#

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

pine cape
#

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

stiff quiver
stiff quiver
#

πŸ€”

#

@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

#

πŸ€”

bronze elm
pine cape
pine cape
#

it's possible to make it work with u16

bleak ingot
#

What is the purpose of LeafwingInput2 in the Protocol?

pine cape
#

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

bleak ingot
#

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?

pine cape
#

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]
    (each UserAction can have its own set of keybindings)
bleak ingot
#

Ok gotcha. Thanks for the help

stiff dome
#

So you can have a reference

stiff quiver
pine cape
#

@bronze elm the bug with long-lived connections should be fixed now

bronze elm
west garnet
#

@pine cape how would you keep track of player scores and show those scores on the clients screens?

pine cape
#

Create a resource or component on the server that contains all the player scores, and replicate it to other players (with mode='simple')

bronze elm
# pine cape <@912493549575082005> the bug with long-lived connections should be fixed now

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.

GitHub

Contribute to panjeet/networked_cube_test development by creating an account on GitHub.

pine cape
#

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)

pine cape
#

Also check if it works well without prediciton/interpolation I guess; or in host-server mode

pine cape
#

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)

stiff quiver
pine cape
#

Wow, weird

bronze elm
pine cape
#

Awesome!

west garnet
#

@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

pine cape
#

You have to enable the 'serialize' feature on bevy so that Transform implements the serialize trait

west garnet
#

okay and how do i do that? πŸ˜…

west garnet
#

alright tnx it works now πŸ‘

pine cape
#

@dull lion @jade ember I got my lobby example working!

  • The ClientConnection and ServerConnection configs 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 game button, the game is hosted either by the dedicated server (each lobby has its own Room ) or by one of the players which will act as host temporarily during the duration of the game
peak nymph
#

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

bronze elm
pine cape
#

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)

peak nymph
#

okay sounds good, no worries! Just wanted to make sure I wasn't going insane πŸ˜„

pine cape
#

merged!

bronze elm
pine cape
#

Very cool!

peak nymph
#

Nice! I took inspiration from Rust for my procgen map, unfortunately that’s about all I have working okayish at the moment lol

jade ember
#

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

pine cape
#

no worries, focus on your final projects πŸ™‚

west garnet
#

@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

pine cape
#

Yep, the lobby example is part of an unreleased version (in the main branch), not part of 0.13.0!

west garnet
#

oke good to know, then i'll patiently wait for the release πŸ™‚

pine cape
west garnet
#

alright tnx

peak nymph
#

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

pine cape
#

So you just start the server and you see this error?

peak nymph
#

yep

pine cape
#

which IO are you using? webtransport?

peak nymph
#

udp and websocket

pine cape
#

you just start the server once and nothing else?

#

i'm not able to reproduce this

peak nymph
#

yea let me double check that the port isn't taken by something else

#

hmm same thing on a different port

#

ill keep digging..

pine cape
pine cape
#

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

peak nymph