mirror of
https://github.com/eliasstepanik/voxel-simulation.git
synced 2026-01-11 05:48:29 +00:00
Basic Client server combo
This commit is contained in:
parent
bc18013601
commit
8f86a7997f
@ -17,4 +17,5 @@ bevy_render = "0.15.0"
|
|||||||
bevy_window = "0.15.0"
|
bevy_window = "0.15.0"
|
||||||
egui_dock = "0.14.0"
|
egui_dock = "0.14.0"
|
||||||
spacetimedb-sdk = "1.0"
|
spacetimedb-sdk = "1.0"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
random_word = { version = "0.5.0", features = ["en"] }
|
||||||
@ -7,7 +7,7 @@ use crate::helper::*;
|
|||||||
use bevy_egui::EguiSet;
|
use bevy_egui::EguiSet;
|
||||||
use bevy_render::extract_resource::ExtractResourcePlugin;
|
use bevy_render::extract_resource::ExtractResourcePlugin;
|
||||||
use spacetimedb_sdk::{credentials, DbContext, Error, Event, Identity, Status, Table, TableWithPrimaryKey};
|
use spacetimedb_sdk::{credentials, DbContext, Error, Event, Identity, Status, Table, TableWithPrimaryKey};
|
||||||
use crate::helper::database::setup_database;
|
use crate::plugins::network::systems::database::setup_database;
|
||||||
use crate::module_bindings::DbConnection;
|
use crate::module_bindings::DbConnection;
|
||||||
|
|
||||||
pub struct AppPlugin;
|
pub struct AppPlugin;
|
||||||
|
|||||||
@ -1,119 +0,0 @@
|
|||||||
|
|
||||||
use bevy::prelude::{Commands, Resource};
|
|
||||||
use bevy::utils::info;
|
|
||||||
use spacetimedb_sdk::{credentials, DbContext, Error, Event, Identity, Status, Table, TableWithPrimaryKey};
|
|
||||||
use crate::module_bindings::*;
|
|
||||||
|
|
||||||
/// The URI of the SpacetimeDB instance hosting our chat module.
|
|
||||||
const HOST: &str = "http://192.168.178.10:3000";
|
|
||||||
|
|
||||||
/// The database name we chose when we published our module.
|
|
||||||
const DB_NAME: &str = "horror-game";
|
|
||||||
|
|
||||||
#[derive(Resource)]
|
|
||||||
pub struct DbConnectionResource(pub(crate) DbConnection);
|
|
||||||
|
|
||||||
pub fn setup_database(mut commands: Commands) {
|
|
||||||
// Call your connection function and insert the connection as a resource.
|
|
||||||
let ctx = connect_to_db();
|
|
||||||
|
|
||||||
register_callbacks(&ctx);
|
|
||||||
|
|
||||||
// Subscribe to SQL queries in order to construct a local partial replica of the database.
|
|
||||||
subscribe_to_tables(&ctx);
|
|
||||||
|
|
||||||
// Spawn a thread, where the connection will process messages and invoke callbacks.
|
|
||||||
ctx.run_threaded();
|
|
||||||
|
|
||||||
|
|
||||||
commands.insert_resource(DbConnectionResource(ctx));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Register subscriptions for all rows of both tables.
|
|
||||||
fn subscribe_to_tables(ctx: &DbConnection) {
|
|
||||||
ctx.subscription_builder()
|
|
||||||
.on_applied(on_sub_applied)
|
|
||||||
.on_error(on_sub_error)
|
|
||||||
.subscribe(["SELECT * FROM physics_world"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Our `on_subscription_applied` callback:
|
|
||||||
/// sort all past messages and print them in timestamp order.
|
|
||||||
fn on_sub_applied(ctx: &SubscriptionEventContext) {
|
|
||||||
println!("Fully connected and all subscriptions applied.");
|
|
||||||
println!("Use /name to set your name, or type a message!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Or `on_error` callback:
|
|
||||||
/// print the error, then exit the process.
|
|
||||||
fn on_sub_error(_ctx: &ErrorContext, err: Error) {
|
|
||||||
eprintln!("Subscription failed: {}", err);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connect_to_db() -> DbConnection {
|
|
||||||
DbConnection::builder()
|
|
||||||
// Register our `on_connect` callback, which will save our auth token.
|
|
||||||
.on_connect(on_connected)
|
|
||||||
// Register our `on_connect_error` callback, which will print a message, then exit the process.
|
|
||||||
/*.on_connect_error(on_connect_error)*/
|
|
||||||
// Our `on_disconnect` callback, which will print a message, then exit the process.
|
|
||||||
.on_disconnect( on_disconnected)
|
|
||||||
// If the user has previously connected, we'll have saved a token in the `on_connect` callback.
|
|
||||||
// In that case, we'll load it and pass it to `with_token`,
|
|
||||||
// so we can re-authenticate as the same `Identity`.
|
|
||||||
.with_token(creds_store().load().expect("Error loading credentials"))
|
|
||||||
// Set the database name we chose when we called `spacetime publish`.
|
|
||||||
.with_module_name(DB_NAME)
|
|
||||||
// Set the URI of the SpacetimeDB host that's running our database.
|
|
||||||
.with_uri(HOST)
|
|
||||||
// Finalize configuration and connect!
|
|
||||||
.build()
|
|
||||||
.expect("Failed to connect")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register all the callbacks our app will use to respond to database events.
|
|
||||||
fn register_callbacks(ctx: &DbConnection) {
|
|
||||||
|
|
||||||
/*// When a new message is received, print it.
|
|
||||||
ctx.db.message().on_insert(on_message_inserted);*/
|
|
||||||
|
|
||||||
/*// When we fail to set our name, print a warning.
|
|
||||||
ctx.reducers.on_set_name(on_name_set);*/
|
|
||||||
|
|
||||||
// When we fail to send a message, print a warning.
|
|
||||||
/*ctx.reducers.on_send_message(on_message_sent);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn creds_store() -> credentials::File {
|
|
||||||
credentials::File::new("quickstart-chat")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Our `on_connect` callback: save our credentials to a file.
|
|
||||||
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.
|
|
||||||
fn on_connect_error(_ctx: &ErrorContext, err: Error) {
|
|
||||||
eprintln!("Connection error: {:?}", err);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Our `on_disconnect` callback: print a note, then exit the process.
|
|
||||||
fn on_disconnected(_ctx: &ErrorContext, err: Option<Error>) {
|
|
||||||
if let Some(err) = err {
|
|
||||||
eprintln!("Disconnected: {}", err);
|
|
||||||
std::process::exit(1);
|
|
||||||
} else {
|
|
||||||
println!("Disconnected.");
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +1,2 @@
|
|||||||
pub mod debug_gizmos;
|
pub mod debug_gizmos;
|
||||||
pub mod egui_dock;
|
pub mod egui_dock;
|
||||||
pub mod database;
|
|
||||||
|
|||||||
@ -4,6 +4,9 @@ use bevy::math::Vec3;
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_render::camera::{Exposure, PhysicalCameraParameters, Projection};
|
use bevy_render::camera::{Exposure, PhysicalCameraParameters, Projection};
|
||||||
use bevy_window::CursorGrabMode;
|
use bevy_window::CursorGrabMode;
|
||||||
|
use random_word::Lang;
|
||||||
|
use crate::module_bindings::set_name;
|
||||||
|
use crate::plugins::network::systems::database::DbConnectionResource;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct CameraController {
|
pub struct CameraController {
|
||||||
@ -54,6 +57,7 @@ pub fn camera_controller_system(
|
|||||||
mut windows: Query<&mut Window>,
|
mut windows: Query<&mut Window>,
|
||||||
mut query: Query<(&mut Transform, &mut CameraController)>,
|
mut query: Query<(&mut Transform, &mut CameraController)>,
|
||||||
mut app_exit_events: EventWriter<AppExit>,
|
mut app_exit_events: EventWriter<AppExit>,
|
||||||
|
mut ctx: ResMut<DbConnectionResource>,
|
||||||
) {
|
) {
|
||||||
let mut window = windows.single_mut();
|
let mut window = windows.single_mut();
|
||||||
let (mut transform, mut controller) = query.single_mut();
|
let (mut transform, mut controller) = query.single_mut();
|
||||||
@ -92,6 +96,12 @@ pub fn camera_controller_system(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let word = random_word::get(Lang::En);
|
||||||
|
|
||||||
|
if keyboard_input.just_pressed(KeyCode::KeyQ) {
|
||||||
|
ctx.0.reducers.set_name(word.to_string()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// ====================
|
// ====================
|
||||||
// 3) Handle Keyboard Movement (WASD, Space, Shift)
|
// 3) Handle Keyboard Movement (WASD, Space, Shift)
|
||||||
// ====================
|
// ====================
|
||||||
@ -147,6 +157,8 @@ pub fn camera_controller_system(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// =======================
|
// =======================
|
||||||
// 7) Exit on Escape
|
// 7) Exit on Escape
|
||||||
// =======================
|
// =======================
|
||||||
|
|||||||
@ -1,18 +1,15 @@
|
|||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use crate::helper::database::DbConnectionResource;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pub fn init(mut commands: Commands) {
|
||||||
|
|
||||||
pub fn init(mut commands: Commands, db_connection: Res<DbConnectionResource>) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fixed_update(time: Res<Time>, db_connection: Res<DbConnectionResource>) {
|
pub fn fixed_update(time: Res<Time>,) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(time: Res<Time>, db_connection: Res<DbConnectionResource>) {
|
pub fn update(time: Res<Time>,) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
pub mod camera;
|
pub mod camera;
|
||||||
pub mod environment;
|
pub mod environment;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
pub mod network;
|
||||||
|
|
||||||
|
|||||||
2
client/src/plugins/network/mod.rs
Normal file
2
client/src/plugins/network/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub(crate) mod systems;
|
||||||
|
mod network_plugin;
|
||||||
13
client/src/plugins/network/network_plugin.rs
Normal file
13
client/src/plugins/network/network_plugin.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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 crate::plugins::network::systems::database::setup_database;
|
||||||
|
|
||||||
|
pub struct NetworkPlugin;
|
||||||
|
impl Plugin for NetworkPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(Startup, setup_database);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
client/src/plugins/network/systems/callbacks.rs
Normal file
50
client/src/plugins/network/systems/callbacks.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
use bevy::log::{error, info};
|
||||||
|
use spacetimedb_sdk::Status;
|
||||||
|
use crate::module_bindings::{EventContext, Player, ReducerEventContext, RemoteDbContext};
|
||||||
|
|
||||||
|
/// Our `User::on_insert` callback:
|
||||||
|
/// if the user is online, print a notification.
|
||||||
|
pub fn on_user_inserted(_ctx: &EventContext, user: &Player) {
|
||||||
|
if user.online {
|
||||||
|
info!("User {} connected.", user_name_or_identity(user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn user_name_or_identity(user: &Player) -> String {
|
||||||
|
user.name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| user.identity.to_hex().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Our `User::on_update` callback:
|
||||||
|
/// print a notification about name and status changes.
|
||||||
|
pub fn on_user_updated(_ctx: &EventContext, old: &Player, new: &Player) {
|
||||||
|
if old.name != new.name {
|
||||||
|
info!(
|
||||||
|
"User {} renamed to {}.",
|
||||||
|
user_name_or_identity(old),
|
||||||
|
user_name_or_identity(new)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if old.online && !new.online {
|
||||||
|
info!("User {} disconnected.", user_name_or_identity(new));
|
||||||
|
}
|
||||||
|
if !old.online && new.online {
|
||||||
|
info!("User {} connected.", user_name_or_identity(new));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Our `on_set_name` callback: print a warning if the reducer failed.
|
||||||
|
pub fn on_name_set(ctx: &ReducerEventContext, name: &String) {
|
||||||
|
if let Status::Failed(err) = &ctx.event.status {
|
||||||
|
error!("Failed to change name to {:?}: {}", name, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Our `on_send_message` callback: print a warning if the reducer failed.
|
||||||
|
pub fn on_message_sent(ctx: &ReducerEventContext, text: &String) {
|
||||||
|
if let Status::Failed(err) = &ctx.event.status {
|
||||||
|
error!("Failed to send message {:?}: {}", text, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
client/src/plugins/network/systems/connection.rs
Normal file
31
client/src/plugins/network/systems/connection.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use spacetimedb_sdk::{credentials, Error, Identity};
|
||||||
|
use crate::module_bindings::{DbConnection, ErrorContext};
|
||||||
|
|
||||||
|
|
||||||
|
pub fn creds_store() -> credentials::File {
|
||||||
|
credentials::File::new("token")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Our `on_connect` callback: save our credentials to a file.
|
||||||
|
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.
|
||||||
|
pub fn on_connect_error(_ctx: &ErrorContext, err: Error) {
|
||||||
|
eprintln!("Connection error: {:?}", err);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Our `on_disconnect` callback: print a note, then exit the process.
|
||||||
|
pub fn on_disconnected(_ctx: &ErrorContext, err: Option<Error>) {
|
||||||
|
if let Some(err) = err {
|
||||||
|
eprintln!("Disconnected: {}", err);
|
||||||
|
std::process::exit(1);
|
||||||
|
} else {
|
||||||
|
println!("Disconnected.");
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
61
client/src/plugins/network/systems/database.rs
Normal file
61
client/src/plugins/network/systems/database.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
use bevy::prelude::{Commands, Resource};
|
||||||
|
use bevy::utils::info;
|
||||||
|
use spacetimedb_sdk::{credentials, DbContext, Error, Event, Identity, Status, Table, TableWithPrimaryKey};
|
||||||
|
use crate::module_bindings::*;
|
||||||
|
use crate::plugins::network::systems::callbacks::*;
|
||||||
|
use crate::plugins::network::systems::connection::*;
|
||||||
|
use crate::plugins::network::systems::subscriptions::*;
|
||||||
|
|
||||||
|
/// The URI of the SpacetimeDB instance hosting our chat module.
|
||||||
|
const HOST: &str = "http://192.168.178.10:3000";
|
||||||
|
|
||||||
|
/// The database name we chose when we published our module.
|
||||||
|
const DB_NAME: &str = "horror-game-test";
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct DbConnectionResource(pub(crate) DbConnection);
|
||||||
|
|
||||||
|
pub fn setup_database(mut commands: Commands) {
|
||||||
|
// Call your connection function and insert the connection as a resource.
|
||||||
|
let ctx = connect_to_db();
|
||||||
|
register_callbacks(&ctx);
|
||||||
|
subscribe_to_tables(&ctx);
|
||||||
|
ctx.run_threaded();
|
||||||
|
commands.insert_resource(DbConnectionResource(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Register subscriptions for all rows of both tables
|
||||||
|
|
||||||
|
fn connect_to_db() -> DbConnection {
|
||||||
|
DbConnection::builder()
|
||||||
|
.on_connect(on_connected)
|
||||||
|
.on_connect_error(on_connect_error)
|
||||||
|
.on_disconnect( on_disconnected)
|
||||||
|
.with_module_name(DB_NAME)
|
||||||
|
.with_uri(HOST)
|
||||||
|
.build()
|
||||||
|
.expect("Failed to connect")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Register all the callbacks our app will use to respond to database events.
|
||||||
|
fn register_callbacks(ctx: &DbConnection) {
|
||||||
|
// When a new user joins, print a notification.
|
||||||
|
ctx.db.player().on_insert(on_user_inserted);
|
||||||
|
|
||||||
|
// When a user's status changes, print a notification.
|
||||||
|
ctx.db.player().on_update(on_user_updated);
|
||||||
|
|
||||||
|
// When we fail to set our name, print a warning.
|
||||||
|
ctx.reducers.on_set_name(on_name_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscribe_to_tables(ctx: &DbConnection) {
|
||||||
|
ctx.subscription_builder()
|
||||||
|
.on_applied(on_sub_applied)
|
||||||
|
.on_error(on_sub_error)
|
||||||
|
.subscribe(["SELECT * FROM player"]);
|
||||||
|
}
|
||||||
4
client/src/plugins/network/systems/mod.rs
Normal file
4
client/src/plugins/network/systems/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub mod database;
|
||||||
|
mod connection;
|
||||||
|
mod callbacks;
|
||||||
|
mod subscriptions;
|
||||||
23
client/src/plugins/network/systems/subscriptions.rs
Normal file
23
client/src/plugins/network/systems/subscriptions.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use bevy::prelude::info;
|
||||||
|
use spacetimedb_sdk::{Error, Table};
|
||||||
|
use crate::module_bindings::{ErrorContext, PlayerTableAccess, SubscriptionEventContext};
|
||||||
|
|
||||||
|
/// Our `on_subscription_applied` callback:
|
||||||
|
/// sort all past messages and print them in timestamp order.
|
||||||
|
pub fn on_sub_applied(ctx: &SubscriptionEventContext) {
|
||||||
|
|
||||||
|
let mut players = ctx.db.player().iter().collect::<Vec<_>>();
|
||||||
|
players.sort_by_key(|p| p.name.clone());
|
||||||
|
for player in players {
|
||||||
|
println!("Player {:?} online", player.name);
|
||||||
|
}
|
||||||
|
println!("Fully connected and all subscriptions applied.");
|
||||||
|
println!("Use /name to set your name, or type a message!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Or `on_error` callback:
|
||||||
|
/// print the error, then exit the process.
|
||||||
|
pub fn on_sub_error(_ctx: &ErrorContext, err: Error) {
|
||||||
|
eprintln!("Subscription failed: {}", err);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
@ -2,6 +2,6 @@
|
|||||||
@echo off
|
@echo off
|
||||||
REM Script to publish the horror-game project using spacetime
|
REM Script to publish the horror-game project using spacetime
|
||||||
|
|
||||||
spacetime publish -c --project-path server horror-game -y
|
spacetime publish -c --project-path server horror-game-test -y
|
||||||
rm client\src\module_bindings\*
|
rm client\src\module_bindings\*
|
||||||
spacetime generate --lang rust --out-dir client/src/module_bindings --project-path server
|
spacetime generate --lang rust --out-dir client/src/module_bindings --project-path server
|
||||||
@ -1,10 +1,47 @@
|
|||||||
|
mod types;
|
||||||
|
|
||||||
|
use spacetimedb::{reducer, ReducerContext, SpacetimeType, Table};
|
||||||
|
use crate::types::player::{player, Player};
|
||||||
|
|
||||||
|
#[spacetimedb::table(name = config, public)]
|
||||||
|
pub struct Config {
|
||||||
|
#[primary_key]
|
||||||
|
pub id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[spacetimedb::reducer]
|
||||||
|
pub fn test(ctx: &ReducerContext) -> Result<(), String> {
|
||||||
|
log::debug!("This reducer was called by {}.", ctx.sender);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
use std::time::Duration;
|
#[reducer(client_connected)]
|
||||||
use spacetimedb::{ReducerContext, ScheduleAt, Table};
|
// Called when a client connects to the SpacetimeDB
|
||||||
|
pub fn client_connected(ctx: &ReducerContext) {
|
||||||
#[spacetimedb::reducer(init)]
|
if let Some(player) = ctx.db.player().identity().find(ctx.sender) {
|
||||||
pub fn init(ctx: &ReducerContext) {
|
// If this is a returning player, i.e. we already have a `player` with this `Identity`,
|
||||||
log::info!("Initializing...");
|
// set `online: true`, but leave `name` and `identity` unchanged.
|
||||||
|
ctx.db.player().identity().update(Player { online: true, ..player });
|
||||||
|
} else {
|
||||||
|
// If this is a new player, create a `player` row for the `Identity`,
|
||||||
|
// which is online, but hasn't set a name.
|
||||||
|
ctx.db.player().insert(Player {
|
||||||
|
name: None,
|
||||||
|
identity: ctx.sender,
|
||||||
|
online: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 });
|
||||||
|
} else {
|
||||||
|
// This branch should be unreachable,
|
||||||
|
// as it doesn't make sense for a client to disconnect without connecting first.
|
||||||
|
log::warn!("Disconnect event for unknown Player with identity {:?}", ctx.sender);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
2
server/src/types/mod.rs
Normal file
2
server/src/types/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod vec3;
|
||||||
|
pub mod player;
|
||||||
31
server/src/types/player.rs
Normal file
31
server/src/types/player.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use spacetimedb::{reducer, Identity, ReducerContext};
|
||||||
|
|
||||||
|
#[spacetimedb::table(name = player, public)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Player {
|
||||||
|
#[primary_key]
|
||||||
|
pub identity: Identity,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub online: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[reducer]
|
||||||
|
/// Clients invoke this reducer to set their user names.
|
||||||
|
pub fn set_name(ctx: &ReducerContext, name: String) -> Result<(), String> {
|
||||||
|
let name = validate_name(name)?;
|
||||||
|
if let Some(user) = ctx.db.player().identity().find(ctx.sender) {
|
||||||
|
ctx.db.player().identity().update(Player { name: Some(name), ..user });
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Cannot set name for unknown user".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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() {
|
||||||
|
Err("Names must not be empty".to_string())
|
||||||
|
} else {
|
||||||
|
Ok(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
server/src/types/vec3.rs
Normal file
20
server/src/types/vec3.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use spacetimedb::SpacetimeType;
|
||||||
|
|
||||||
|
#[derive(SpacetimeType, Clone, Debug)]
|
||||||
|
pub struct DbVector3 {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[spacetimedb::table(name = entity, public)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Entity {
|
||||||
|
// The `auto_inc` attribute indicates to SpacetimeDB that
|
||||||
|
// this value should be determined by SpacetimeDB on insert.
|
||||||
|
#[auto_inc]
|
||||||
|
#[primary_key]
|
||||||
|
pub entity_id: u32,
|
||||||
|
pub position: DbVector3,
|
||||||
|
pub mass: u32,
|
||||||
|
}
|
||||||
2
watch_logs.bat
Normal file
2
watch_logs.bat
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
for /l %g in () do @( spacetime logs horror-game-test )
|
||||||
Loading…
x
Reference in New Issue
Block a user