mirror of
https://github.com/eliasstepanik/voxel-simulation.git
synced 2026-01-11 05:48:29 +00:00
Synced Object with Auto remove + Player Sync
This commit is contained in:
parent
86aa2c77be
commit
9eabdf1be9
@ -6,7 +6,7 @@ use bevy_render::camera::{Exposure, PhysicalCameraParameters, Projection};
|
||||
use bevy_window::CursorGrabMode;
|
||||
use rand::Rng;
|
||||
use random_word::Lang;
|
||||
use crate::module_bindings::{set_name, spawn_entity, DbVector3};
|
||||
use crate::module_bindings::{set_name, set_position, spawn_entity, DbVector3};
|
||||
use crate::plugins::network::systems::database::DbConnectionResource;
|
||||
|
||||
#[derive(Component)]
|
||||
@ -28,7 +28,10 @@ impl Default for CameraController {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup(mut commands: Commands) {
|
||||
pub fn setup(mut commands: Commands,
|
||||
db_resource: Res<DbConnectionResource>,) {
|
||||
|
||||
|
||||
commands.spawn((
|
||||
Transform::from_xyz(0.0, 0.0, 10.0), // initial f32
|
||||
GlobalTransform::default(),
|
||||
@ -45,6 +48,7 @@ pub fn setup(mut commands: Commands) {
|
||||
sensitivity_iso: 100.0,
|
||||
sensor_height: 0.01866,
|
||||
}),
|
||||
|
||||
));
|
||||
}
|
||||
|
||||
@ -159,6 +163,13 @@ pub fn camera_controller_system(
|
||||
let distance = controller.speed as f64 * delta_seconds;
|
||||
transform.translation += direction * distance as f32;
|
||||
|
||||
ctx.0.reducers.set_position(DbVector3{
|
||||
x: transform.translation.x,
|
||||
y: transform.translation.y,
|
||||
z: transform.translation.z,
|
||||
}).expect("TODO: panic message");
|
||||
|
||||
|
||||
// =========================
|
||||
// 4) Lock/Unlock Mouse (L)
|
||||
// =========================
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
use bevy::app::{App, Plugin, Startup};
|
||||
use bevy::color::palettes::basic::{GREEN, YELLOW};
|
||||
use bevy::color::palettes::css::RED;
|
||||
use bevy::prelude::*;
|
||||
use crate::plugins::environment::systems::environment_system::*;
|
||||
use bevy::app::{App, Plugin};
|
||||
pub struct EnvironmentPlugin;
|
||||
impl Plugin for EnvironmentPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, init);
|
||||
app.add_systems(Update, sync_entities_system);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,93 +1,9 @@
|
||||
|
||||
use bevy::prelude::*;
|
||||
use spacetimedb_sdk::Table;
|
||||
use crate::module_bindings::{entity_table, DbVector3, EntityTableAccess};
|
||||
use crate::plugins::network::systems::database::DbConnectionResource;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct EntityDto {
|
||||
|
||||
pub entity_id: u32,
|
||||
pub position: DbVector3,
|
||||
}
|
||||
|
||||
pub fn init(mut commands: Commands,
|
||||
ctx: Res<DbConnectionResource>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,) {
|
||||
|
||||
|
||||
|
||||
let debug_material = materials.add(StandardMaterial { ..default() });
|
||||
|
||||
for entity in ctx.0.db.entity().iter() {
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Cuboid::default()),),
|
||||
MeshMaterial3d(debug_material.clone ()),
|
||||
Transform::from_xyz(
|
||||
entity.position.x,
|
||||
entity.position.y,
|
||||
entity.position.z,
|
||||
),
|
||||
EntityDto{
|
||||
entity_id: entity.entity_id,
|
||||
position: entity.position,
|
||||
}
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn sync_entities_system(
|
||||
mut commands: Commands,
|
||||
db_resource: Res<DbConnectionResource>,
|
||||
mut query: Query<(Entity, &mut Transform, &mut EntityDto)>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
let ctx = &db_resource.0.db;
|
||||
|
||||
// For each entity record in DB, see if it exists in ECS:
|
||||
for db_entity in ctx.entity().iter() {
|
||||
// Try to find a matching entity in ECS by comparing `entity_id`
|
||||
if let Some((entity, mut transform, mut dto)) = query
|
||||
.iter_mut()
|
||||
.find(|(_, _, dto)| dto.entity_id == db_entity.entity_id)
|
||||
{
|
||||
// It already exists. Perhaps update ECS data to match DB:
|
||||
dto.position = db_entity.position;
|
||||
transform.translation = Vec3::new(
|
||||
dto.position.x,
|
||||
dto.position.y,
|
||||
dto.position.z,
|
||||
);
|
||||
// ...do any other sync logic
|
||||
} else {
|
||||
|
||||
|
||||
let debug_material = materials.add(StandardMaterial { ..default() });
|
||||
// Not found in ECS, so spawn a new entity
|
||||
commands.spawn((
|
||||
EntityDto {
|
||||
entity_id: db_entity.entity_id,
|
||||
position: db_entity.position.clone(),
|
||||
},
|
||||
// Create an initial transform using DB data
|
||||
Transform::from_xyz(
|
||||
db_entity.position.x,
|
||||
db_entity.position.y,
|
||||
db_entity.position.z,
|
||||
),
|
||||
GlobalTransform::default(),
|
||||
Mesh3d(meshes.add(Cuboid::default()),),
|
||||
MeshMaterial3d(debug_material.clone ()),
|
||||
));
|
||||
}
|
||||
}
|
||||
pub fn setup(mut commands: Commands) {
|
||||
|
||||
}
|
||||
|
||||
pub fn update(time: Res<Time>,) {
|
||||
|
||||
@ -4,10 +4,13 @@ use bevy::color::palettes::css::RED;
|
||||
use bevy::prelude::*;
|
||||
use crate::plugins::environment::systems::environment_system::*;
|
||||
use crate::plugins::network::systems::database::setup_database;
|
||||
use crate::plugins::network::systems::entities::*;
|
||||
|
||||
pub struct NetworkPlugin;
|
||||
impl Plugin for NetworkPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(PreStartup, setup_database);
|
||||
/*app.add_systems(Startup, init);
|
||||
app.add_systems(Update, sync_entities_system);*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ pub fn on_connected(_ctx: &DbConnection, _identity: Identity, token: &str) {
|
||||
if let Err(e) = creds_store().save(token) {
|
||||
eprintln!("Failed to save credentials: {:?}", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Our `on_connect_error` callback: print the error, then exit the process.
|
||||
|
||||
119
client/src/plugins/network/systems/entities.rs
Normal file
119
client/src/plugins/network/systems/entities.rs
Normal file
@ -0,0 +1,119 @@
|
||||
use std::collections::HashSet;
|
||||
use bevy::math::Vec3;
|
||||
use bevy::pbr::{MeshMaterial3d, StandardMaterial};
|
||||
use bevy::prelude::{default, Commands, Component, Cuboid, DespawnRecursiveExt, Entity, GlobalTransform, Mesh, Query, Res, ResMut, Sphere, Transform};
|
||||
use bevy_asset::Assets;
|
||||
use bevy_render::mesh::Mesh3d;
|
||||
use spacetimedb_sdk::Table;
|
||||
use crate::module_bindings::{DbVector3, EntityTableAccess, EntityType};
|
||||
use crate::plugins::network::systems::database::DbConnectionResource;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct EntityDto {
|
||||
|
||||
pub entity_id: u32,
|
||||
pub position: DbVector3,
|
||||
}
|
||||
|
||||
pub fn init(mut commands: Commands,
|
||||
ctx: Res<DbConnectionResource>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,) {
|
||||
|
||||
|
||||
|
||||
let debug_material = materials.add(StandardMaterial { ..default() });
|
||||
|
||||
for entity in ctx.0.db.entity().iter() {
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Cuboid::default()),),
|
||||
MeshMaterial3d(debug_material.clone ()),
|
||||
Transform::from_xyz(
|
||||
entity.position.x,
|
||||
entity.position.y,
|
||||
entity.position.z,
|
||||
),
|
||||
EntityDto{
|
||||
entity_id: entity.entity_id,
|
||||
position: entity.position,
|
||||
}
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// System that syncs DB entities with the Bevy ECS
|
||||
pub fn sync_entities_system(
|
||||
mut commands: Commands,
|
||||
db_resource: Res<DbConnectionResource>,
|
||||
|
||||
// We need the Entity handle for potential despawning,
|
||||
// plus mutable references if we want to update Transform/EntityDto
|
||||
mut query: Query<(Entity, &mut Transform, &mut EntityDto)>,
|
||||
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// --- 1) Collect DB entities and build a set of IDs ---
|
||||
let db_entities = db_resource.0.db.entity();
|
||||
let db_ids: HashSet<u32> = db_entities.iter().map(|e| e.entity_id).collect();
|
||||
|
||||
// --- 2) For each DB entity, update or spawn in ECS ---
|
||||
for db_entity in db_entities.iter() {
|
||||
|
||||
// Try to find a matching ECS entity by entity_id
|
||||
if let Some((_, mut transform, mut dto)) =
|
||||
query.iter_mut().find(|(_, _, dto)| dto.entity_id == db_entity.entity_id)
|
||||
{
|
||||
// Update fields
|
||||
dto.position = db_entity.position.clone();
|
||||
transform.translation = Vec3::new(
|
||||
db_entity.position.x,
|
||||
db_entity.position.y,
|
||||
db_entity.position.z,
|
||||
);
|
||||
|
||||
} else {
|
||||
// Not found in ECS, so spawn a new entity
|
||||
let debug_material = materials.add(StandardMaterial {
|
||||
// fill out any fields you want
|
||||
..default()
|
||||
});
|
||||
|
||||
// Pick a mesh based on the entity type
|
||||
let entity_type = match db_entity.entity_type {
|
||||
EntityType::Sphere => Mesh3d(meshes.add(Sphere::default())),
|
||||
EntityType::Cube => Mesh3d(meshes.add(Cuboid::default())),
|
||||
EntityType::Custom => todo!(),
|
||||
};
|
||||
|
||||
commands.spawn((
|
||||
EntityDto {
|
||||
entity_id: db_entity.entity_id,
|
||||
position: db_entity.position.clone(),
|
||||
},
|
||||
Transform::from_xyz(
|
||||
db_entity.position.x,
|
||||
db_entity.position.y,
|
||||
db_entity.position.z,
|
||||
),
|
||||
GlobalTransform::default(),
|
||||
entity_type,
|
||||
MeshMaterial3d(debug_material),
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --- 3) Despawn any ECS entity that doesn't exist in the DB anymore ---
|
||||
for (entity, _, dto) in query.iter_mut() {
|
||||
if !db_ids.contains(&dto.entity_id) {
|
||||
// This ECS entity no longer matches anything in the DB => remove it
|
||||
commands.entity(entity).despawn_recursive();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
pub mod database;
|
||||
mod connection;
|
||||
mod callbacks;
|
||||
mod subscriptions;
|
||||
mod subscriptions;
|
||||
pub mod entities;
|
||||
@ -1,7 +1,9 @@
|
||||
mod types;
|
||||
|
||||
use spacetimedb::{reducer, ReducerContext, SpacetimeType, Table};
|
||||
use crate::types::player::{player, Player};
|
||||
use spacetimedb::{reducer, ReducerContext, Table};
|
||||
use crate::types::entity::{entity, entity__TableHandle, Entity, EntityType};
|
||||
use crate::types::player::{player, player__TableHandle, Player};
|
||||
use crate::types::vec3::DbVector3;
|
||||
|
||||
#[spacetimedb::table(name = config, public)]
|
||||
pub struct Config {
|
||||
@ -26,10 +28,21 @@ pub fn client_connected(ctx: &ReducerContext) {
|
||||
} else {
|
||||
// If this is a new player, create a `player` row for the `Identity`,
|
||||
// which is online, but hasn't set a name.
|
||||
let entity = ctx.db.entity().try_insert(Entity{
|
||||
entity_id: 0,
|
||||
position: DbVector3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 10.0,
|
||||
},
|
||||
entity_type: EntityType::Sphere,
|
||||
}).expect("TODO: panic message");
|
||||
|
||||
ctx.db.player().insert(Player {
|
||||
name: None,
|
||||
identity: ctx.sender,
|
||||
online: true,
|
||||
entity_id: entity.entity_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -37,8 +50,20 @@ pub fn client_connected(ctx: &ReducerContext) {
|
||||
#[reducer(client_disconnected)]
|
||||
// Called when a client disconnects from SpacetimeDB
|
||||
pub fn identity_disconnected(ctx: &ReducerContext) {
|
||||
if let Some(player) = ctx.db.player().identity().find(ctx.sender) {
|
||||
ctx.db.player().identity().update(Player { online: false, ..player });
|
||||
|
||||
let entity: &entity__TableHandle = ctx.db.entity();
|
||||
|
||||
|
||||
|
||||
|
||||
if let Some(player_iter) = ctx.db.player().identity().find(ctx.sender) {
|
||||
|
||||
|
||||
|
||||
ctx.db.player().identity().update(Player { online: false, ..player_iter });
|
||||
ctx.db.entity().iter().find(|e| e.entity_id == player_iter.entity_id).iter().for_each(|e| {
|
||||
entity.delete(e.clone());
|
||||
})
|
||||
} else {
|
||||
// This branch should be unreachable,
|
||||
// as it doesn't make sense for a client to disconnect without connecting first.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
use spacetimedb::{Identity, ReducerContext, Table};
|
||||
use spacetimedb::{Identity, ReducerContext, SpacetimeType, Table};
|
||||
use crate::types::vec3::{DbVector3};
|
||||
|
||||
#[spacetimedb::table(name = entity, public)]
|
||||
@ -8,18 +8,28 @@ pub struct Entity {
|
||||
#[auto_inc]
|
||||
#[primary_key]
|
||||
pub entity_id: u32,
|
||||
|
||||
|
||||
pub position: DbVector3,
|
||||
|
||||
pub entity_type: EntityType,
|
||||
}
|
||||
|
||||
#[derive(SpacetimeType, Clone, Debug)]
|
||||
pub enum EntityType {
|
||||
Cube,
|
||||
Sphere,
|
||||
Custom
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn spawn_entity(ctx: &ReducerContext, position: DbVector3) -> Result<(), String> {
|
||||
|
||||
ctx.db.entity().try_insert(Entity {
|
||||
entity_id: 0,
|
||||
position,
|
||||
entity_type: EntityType::Cube,
|
||||
}).expect("TODO: panic message");
|
||||
|
||||
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
pub mod vec3;
|
||||
pub mod player;
|
||||
mod entity;
|
||||
pub mod entity;
|
||||
@ -1,10 +1,16 @@
|
||||
use spacetimedb::{reducer, Identity, ReducerContext};
|
||||
use spacetimedb::{reducer, Identity, ReducerContext, Table};
|
||||
use crate::types::entity::{entity, Entity};
|
||||
use crate::types::vec3::DbVector3;
|
||||
|
||||
#[spacetimedb::table(name = player, public)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Player {
|
||||
#[primary_key]
|
||||
pub identity: Identity,
|
||||
|
||||
#[index(btree)]
|
||||
pub entity_id: u32,
|
||||
|
||||
pub name: Option<String>,
|
||||
pub online: bool,
|
||||
}
|
||||
@ -21,6 +27,20 @@ pub fn set_name(ctx: &ReducerContext, name: String) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[reducer]
|
||||
/// Clients invoke this reducer to set their user names.
|
||||
pub fn set_position(ctx: &ReducerContext, position: DbVector3) -> Result<(), String> {
|
||||
if let Some(entity) = ctx.db.entity().iter().find(|e| e.entity_id == ctx.db.player().identity().find(ctx.sender).unwrap().entity_id) {
|
||||
ctx.db.entity().entity_id()
|
||||
.update(Entity{
|
||||
position,
|
||||
..entity
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Takes a name and checks if it's acceptable as a user's name.
|
||||
fn validate_name(name: String) -> Result<String, String> {
|
||||
if name.is_empty() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user