Network networking

This commit is contained in:
Iaphetes 2025-11-02 15:21:24 +01:00
parent c9a7e071cd
commit eafc7cfced
14 changed files with 334 additions and 36 deletions

9
Cargo.lock generated
View file

@ -4693,6 +4693,15 @@ dependencies = [
[[package]]
name = "orthros-client"
version = "0.1.0"
dependencies = [
"bevy",
"bevy_matchbox",
"orthros_network",
"rand 0.9.2",
"rmp-serde",
"serde",
"statrs",
]
[[package]]
name = "orthros-server"

View file

@ -4,3 +4,12 @@ version = "0.1.0"
edition = "2024"
[dependencies]
bevy = "0.17"
rand = "0.9.1"
# rusqlite = "0.37.0"
statrs = "0.18.0"
orthros_network = {path = "../orthros-network"}
bevy_matchbox = {version = "0.13", features= ["signaling"]}
serde = { version = "1.0", features = ["derive"] }
rmp-serde = "1.3.0"

View file

@ -1,3 +1,292 @@
use bevy::{
asset::RenderAssetUsages,
camera::RenderTarget,
color::palettes::css::*,
core_pipeline::tonemapping::Tonemapping,
pbr::wireframe::{Wireframe, WireframePlugin},
post_process::bloom::Bloom,
prelude::*,
render::{render_resource::*, view::Hdr},
};
use bevy_matchbox::MatchboxSocket;
use orthros_network::{
generic::{EntityID, NetworkObject},
starchart::star::{StarClass, StarInfo},
};
use rmp_serde::Deserializer;
use serde::Deserialize;
use std::f32::consts::PI;
const CHANNEL_ID: usize = 0;
fn main() {
println!("Hello, world!");
App::new()
.add_plugins((DefaultPlugins, WireframePlugin::default()))
.add_systems(Startup, (setup_scene, start_socket))
.add_systems(Update, (receive_messages, update_stars))
.run();
}
fn create_star_system_annotation(
commands: &mut Commands,
asset_server: &Res<AssetServer>,
images: &mut ResMut<Assets<Image>>,
meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<StandardMaterial>>,
star_info: &StarInfo,
) -> (Handle<Mesh>, Handle<StandardMaterial>) {
let size = Extent3d {
width: 1024,
height: 1024,
..default()
};
// This is the texture that will be rendered to.
let mut image = Image::new_fill(
size,
TextureDimension::D2,
&[0, 0, 0, 0],
TextureFormat::Bgra8UnormSrgb,
RenderAssetUsages::default(),
);
// You need to set these texture usage flags in order to use the image as a render target
image.texture_descriptor.usage =
TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT;
let image_handle = images.add(image);
// Light
let texture_camera = commands
.spawn((
Camera2d,
Camera {
target: RenderTarget::Image(image_handle.clone().into()),
clear_color: ClearColorConfig::None,
..default()
},
))
.id();
let border = UiRect::all(Val::Px(40.));
let non_zero = |x, y| x != Val::Px(0.) && y != Val::Px(0.);
let border_size = |x, y| if non_zero(x, y) { f32::MAX } else { 0. };
commands
.spawn((
Node {
// Cover the whole image
width: Val::Percent(100.),
height: Val::Percent(100.),
flex_direction: FlexDirection::Column,
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
BackgroundColor(Color::Srgba(Srgba {
red: 0.0,
green: 0.0,
blue: 0.0,
alpha: 0.0,
})),
Text::new(format!("{}", star_info.name)),
TextFont {
font: asset_server.load("fonts/quantum/quantflt.ttf"),
// This font is loaded and will be used instead of the default font.
font_size: 60.0,
..default()
},
TextShadow::default(),
// Set the justification of the Text
TextLayout::new_with_justify(Justify::Center),
UiTargetCamera(texture_camera),
))
.with_children(|parent| {
// parent.spawn((
// // Accepts a `String` or any type that converts into a `String`, such as `&str`
// Text::new("hello\nbevy!"),
// TextFont {
// // This font is loaded and will be used instead of the default font.
// font_size: 60.0,
// ..default()
// },
// TextShadow::default(),
// // Set the justification of the Text
// TextLayout::new_with_justify(JustifyText::Center),
// // Set the style of the Node itself.
// Node {
// // position_type: PositionType::Relative,
// align_self: AlignSelf::FlexStart,
// // top: Val::Px(5.0),
// // right: Val::Px(5.0),
// ..default()
// },
// ));
parent.spawn((
Node {
width: Val::Px(400.),
height: Val::Px(400.),
border,
margin: UiRect::AUTO,
align_self: AlignSelf::Center,
// align_items: AlignItems::Center,
// justify_content: JustifyContent::Center,
// align_content: AlignContent::Center,
..default()
},
BackgroundColor(Color::Srgba(Srgba {
red: 0.0,
green: 0.0,
blue: 0.0,
alpha: 0.0,
})),
BorderColor::all(RED),
BorderRadius::px(
border_size(border.left, border.top),
border_size(border.right, border.top),
border_size(border.right, border.bottom),
border_size(border.left, border.bottom),
),
// Outline {
// width: Val::Px(6.),
// offset: Val::Px(6.),
// color: Color::WHITE,
// },
));
});
let material_handle = materials.add(StandardMaterial {
// base_color: Color::srgba(1.0, 1.0, 1.0, 0.0),
base_color_texture: Some(image_handle),
unlit: true,
alpha_mode: AlphaMode::Blend,
..default()
});
let aspect = 1.0;
// create a new quad mesh. this is what we will apply the texture to
let quad_width = 10.0;
let quad_handle = meshes.add(Rectangle::new(quad_width, quad_width * aspect));
return (quad_handle, material_handle);
}
fn setup_scene(mut commands: Commands) {
commands.spawn((
Camera3d::default(),
Camera {
clear_color: ClearColorConfig::Custom(Color::from(BLACK)),
..default()
},
Hdr,
Tonemapping::Reinhard, // 2. Using a tonemapper that desaturates to white is recommended
Transform::from_xyz(0.0, 10.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
Bloom::NATURAL, // 3. Enable bloom for the camera
));
}
fn star_material(
star_class: &StarClass,
materials: &mut ResMut<Assets<StandardMaterial>>,
) -> Handle<StandardMaterial> {
match star_class {
StarClass::MType => materials.add(StandardMaterial {
emissive: LinearRgba::rgb(1000.0, 710.0 / 2.0, 424.0 / 2.0), // 4. Put something bright in a dark environment to see the effect
..default()
}),
StarClass::BType => materials.add(StandardMaterial {
emissive: LinearRgba::rgb(635.0 / 2.0, 753.0 / 2.0, 1000.0), // 4. Put something bright in a dark environment to see the effect
..default()
}),
StarClass::GType => materials.add(StandardMaterial {
emissive: LinearRgba::rgb(1000.0 / 2.0, 929.0 / 2.0, 890.0), // 4. Put something bright in a dark environment to see the effect
..default()
}),
}
}
fn spawn_star(
mut commands: &mut Commands,
star_info: &StarInfo,
mut meshes: &mut ResMut<Assets<Mesh>>,
mut materials: &mut ResMut<Assets<StandardMaterial>>,
asset_server: &Res<AssetServer>,
mut images: &mut ResMut<Assets<Image>>,
) {
let mesh = meshes.add(Sphere::new(star_info.diameter).mesh().ico(5).unwrap());
let material = star_material(&star_info.class, &mut materials);
let (quad_handle, material_handle) = create_star_system_annotation(
&mut commands,
&asset_server,
&mut images,
&mut meshes,
&mut materials,
&star_info,
);
commands
.spawn((
star_info.id.clone(),
Mesh3d(mesh.clone()),
MeshMaterial3d(material.clone()),
star_info.transform,
Wireframe,
))
.with_children(|parent| {
parent.spawn((
Mesh3d(quad_handle),
MeshMaterial3d(material_handle.clone()),
Transform::from_xyz(0.0, 0.0, 0.0)
.with_scale(Vec3::splat(0.18))
// .with_scale(Vec3::splat(scale))
.with_rotation(Quat::from_euler(EulerRot::XYZ, -PI / 2.0, 0.0, PI / 2.0)),
// Wireframe,
));
});
}
fn start_socket(mut commands: Commands) {
let socket = MatchboxSocket::new_reliable("ws://localhost:3536/hello");
info!("Created socket");
commands.insert_resource(socket);
}
fn update_stars(
mut commands: Commands,
network_stars: Query<(Entity, &StarInfo)>,
spawned_stars: Query<(Entity, &EntityID)>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
asset_server: Res<AssetServer>,
mut images: ResMut<Assets<Image>>,
) {
for (id, network_star) in network_stars {
if let Some((entity, _)) = spawned_stars
.iter()
.filter(|(_, spawned_id)| **spawned_id == network_star.id)
.last()
{
// let entity_command = commands.get_entity(entity).unwrap();
} else {
spawn_star(
&mut commands,
network_star,
&mut meshes,
&mut materials,
&asset_server,
&mut images,
);
}
//
}
}
fn receive_messages(mut socket: ResMut<MatchboxSocket>, mut commands: Commands) {
for (peer, state) in socket.update_peers() {
info!("{peer}: {state:?}");
}
for (_id, message) in socket.channel_mut(CHANNEL_ID).receive() {
match NetworkObject::deserialize(&mut Deserializer::new(&*message)) {
Ok(message) => {
info!("Received message: {message:?}");
match message {
NetworkObject::Star(star_info) => {
commands.spawn(star_info);
}
}
}
Err(e) => error!("Failed to convert message to string: {e}"),
}
}
}

View file

@ -1,9 +1,9 @@
use crate::starchart::star::StarInfo;
use bevy::ecs::component::Component;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Component, PartialEq, Clone)]
pub struct EntityID(pub u32);
#[derive(Serialize, Deserialize, Debug, Component)]
pub enum ObjectUpdate {
pub enum NetworkObject {
Star(StarInfo),
}

View file

@ -1,3 +1,3 @@
pub mod generic;
pub mod starchart;
pub const SCALEAU: f32 = 1.0;
pub const SCALEAU: f32 = 1000000.0;

View file

@ -6,13 +6,13 @@ use serde::{Deserialize, Serialize};
fn solar_radius_to_ly_scaled(r: f32) -> f32 {
r * SOLARRADIUSTOLY * SCALEAU
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum StarClass {
MType,
BType,
GType,
}
#[derive(Serialize, Deserialize, Debug, Component)]
#[derive(Serialize, Deserialize, Debug, Component, Clone)]
pub struct StarInfo {
pub id: EntityID,
pub name: String,

View file

@ -6,7 +6,7 @@ use bevy::{
};
use bevy_matchbox::{matchbox_signaling::SignalingServer, prelude::*};
use orthros_network::{
generic::EntityID,
generic::{EntityID, NetworkObject},
starchart::star::{STARS, StarClass, StarInfo, star_diameter_range},
};
use rand::{
@ -14,15 +14,16 @@ use rand::{
distr::{Distribution, Uniform},
rng,
};
use rmp_serde::{Deserializer, Serializer};
use serde::{Deserialize, Serialize};
use std::{
collections::HashSet,
net::{Ipv4Addr, SocketAddrV4},
time::Duration,
};
const CHANNEL_ID: usize = 0;
#[derive(Component)]
struct NetworkObject;
#[derive(Component)]
struct Changed;
fn main() {
App::new()
@ -44,10 +45,7 @@ fn main() {
)
.add_systems(
Update,
(
receive_messages,
send_message.run_if(on_timer(Duration::from_secs(5))),
),
(send_message.run_if(on_timer(Duration::from_secs(5))),),
)
.run();
}
@ -74,10 +72,10 @@ fn start_signaling_server(mut commands: Commands) {
fn setup_scene(mut commands: Commands, mut socket: ResMut<MatchboxSocket>) {
let mut spawned_ids: HashSet<u32> = HashSet::new();
for _ in 0..4 {
let x = Uniform::try_from(-2000..2000).unwrap().sample(&mut rng());
let z = Uniform::try_from(-8000..8000).unwrap().sample(&mut rng());
let star_class: StarClass = match rng().random_range(0..2) {
for _ in 0..10 {
let x = Uniform::try_from(-200..200).unwrap().sample(&mut rng());
let z = Uniform::try_from(-800..800).unwrap().sample(&mut rng());
let star_class: StarClass = match rng().random_range(0..3) {
0 => StarClass::MType,
1 => StarClass::BType,
2 => StarClass::GType,
@ -97,7 +95,7 @@ fn setup_scene(mut commands: Commands, mut socket: ResMut<MatchboxSocket>) {
transform: Transform::from_xyz(x as f32 / 100.0, z as f32 / 20000.0, z as f32 / 100.0),
};
commands.spawn((StarInfoComp(star_info), NetworkObject, Changed));
commands.spawn((NetworkObject::Star(star_info), Changed));
let peers: Vec<_> = socket.connected_peers().collect();
for peer in peers {
@ -109,34 +107,27 @@ fn setup_scene(mut commands: Commands, mut socket: ResMut<MatchboxSocket>) {
}
}
}
fn on_client_connect(mut socket: ResMut<MatchboxSocket>, Query<&StarInfo>)
fn start_socket(mut commands: Commands) {
let socket = MatchboxSocket::new_reliable("ws://localhost:3536/hello");
info!("Created socket");
commands.insert_resource(socket);
}
fn send_message(mut socket: ResMut<MatchboxSocket>) {
let peers: Vec<_> = socket.connected_peers().collect();
for peer in peers {
let message = "Hello";
info!("Sending message: {message:?} to {peer}");
socket
.channel_mut(CHANNEL_ID)
.send(message.as_bytes().into(), peer);
}
}
fn receive_messages(mut socket: ResMut<MatchboxSocket>) {
fn send_message(mut socket: ResMut<MatchboxSocket>, network_objects: Query<&NetworkObject>) {
for (peer, state) in socket.update_peers() {
info!("{peer}: {state:?}");
}
for (_id, message) in socket.channel_mut(CHANNEL_ID).receive() {
match std::str::from_utf8(&message) {
Ok(message) => info!("Received message: {message:?}"),
Err(e) => error!("Failed to convert message to string: {e}"),
let peers: Vec<_> = socket.connected_peers().collect();
for peer in peers {
for network_object in network_objects.iter() {
let mut ser_buf: Vec<u8> = Vec::new();
network_object.serialize(&mut Serializer::new(&mut ser_buf));
info!("Sending message: {ser_buf:?} to {peer}");
socket
.channel_mut(CHANNEL_ID)
.send(ser_buf.as_slice().into(), peer);
}
}
}