add generation

This commit is contained in:
Matthieu Jolimaitre 2024-11-19 05:00:12 +01:00
parent b204118b82
commit b5c6067979
10 changed files with 250 additions and 130 deletions

23
Cargo.lock generated
View file

@ -1127,6 +1127,16 @@ version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "ctrlc"
version = "3.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
dependencies = [
"nix",
"windows-sys 0.59.0",
]
[[package]]
name = "cursor-icon"
version = "1.1.0"
@ -1150,6 +1160,7 @@ version = "0.1.0"
dependencies = [
"bevy",
"bevy_eventwork",
"ctrlc",
"itermore",
"rand",
"serde",
@ -1817,6 +1828,18 @@ dependencies = [
"jni-sys",
]
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
"cfg_aliases 0.2.1",
"libc",
]
[[package]]
name = "nonmax"
version = "0.5.5"

View file

@ -8,6 +8,7 @@ bevy = { version = "0.14.2", default-features = false, features = [
"multi_threaded",
] }
bevy_eventwork = "0.9.0"
ctrlc = "3.4.5"
itermore = { version = "0.7.1", features = ["array_chunks"] }
rand = "0.8.5"
serde = { version = "1.0.215", features = ["derive"] }

View file

@ -1,16 +1,11 @@
use bevy_eventwork::{tcp::TcpProvider, ConnectionId, Network};
use bevy_eventwork::{tcp::TcpProvider, Network};
use std::{
io::{stdin, stdout, Stdout},
io::{stdin, stdout},
process,
sync::{mpsc, Arc, Mutex},
thread::{self, JoinHandle},
};
use termion::{
cursor::{self, HideCursor},
event::Key,
input::TermRead,
raw::{IntoRawMode, RawTerminal},
thread,
};
use termion::{clear, cursor, event::Key, input::TermRead, raw::IntoRawMode};
use bevy::prelude::*;
@ -22,7 +17,7 @@ pub struct InputPlugin;
impl Plugin for InputPlugin {
fn build(&self, app: &mut App) {
let (rec, _thread) = spawn_reader();
let rec = spawn_reader();
let rec = Arc::new(Mutex::new(rec));
app.insert_resource(InputReceiver(rec))
.add_event::<KeyEvent>()
@ -37,23 +32,36 @@ pub struct InputReceiver(Arc<Mutex<mpsc::Receiver<Key>>>);
#[derive(Debug, Event)]
pub struct KeyEvent(Key);
fn spawn_reader() -> (mpsc::Receiver<Key>, JoinHandle<()>) {
fn spawn_reader() -> mpsc::Receiver<Key> {
let (tx, rx) = mpsc::channel();
let thread = thread::spawn(move || {
let stdout = stdout().into_raw_mode().unwrap();
let (exit_tx, exit_rx) = mpsc::channel();
thread::spawn(move || {
let raw_mode = stdout().into_raw_mode().unwrap();
println!("{}", cursor::Hide);
exit_rx.recv().ok();
drop(raw_mode);
println!("{}{}", cursor::Restore, clear::All);
process::exit(0);
});
let exit_tx_ = exit_tx.clone();
ctrlc::set_handler(move || {
exit_tx_.send(()).ok();
})
.unwrap();
thread::spawn(move || {
let mut keys = stdin().keys();
while let Some(Ok(key)) = keys.next() {
if key == Key::Esc {
println!("exitting.");
println!("{}", cursor::Restore);
drop(stdout);
process::exit(0);
exit_tx.send(()).ok();
}
tx.send(key).ok();
}
});
(rx, thread)
rx
}
fn try_read_keys(receiver: Res<InputReceiver>, mut writer: EventWriter<KeyEvent>) {

View file

@ -3,7 +3,7 @@ use bevy::{prelude::*, utils::hashbrown::HashSet};
use bevy_eventwork::{tcp::TcpProvider, ConnectionId, Network};
use super::{
physics::{update_physics_collisions, Pos},
physics::{self, Pos},
player::Player,
};
@ -13,7 +13,7 @@ impl Plugin for DisplayPlugin {
fn build(&self, app: &mut App) {
app.add_event::<RenderUpdate>()
// .add_systems(Update, rerender_players)
.add_systems(Update, on_updates.after(update_physics_collisions));
.add_systems(Update, on_updates.after(physics::update_physics_pos));
}
}
@ -35,8 +35,8 @@ fn on_updates(
let mut to_render = HashSet::new();
for RenderUpdate(event_pos) in updates.read() {
for (Player(connection_id), player_pos) in &players {
if player_pos.pos().distance_squared(*event_pos) <= RENDER_DISTANCE_SQ {
to_render.insert((*connection_id, player_pos.pos()));
if player_pos.0.distance_squared(*event_pos) <= RENDER_DISTANCE_SQ {
to_render.insert((*connection_id, player_pos.0));
}
}
}
@ -54,14 +54,14 @@ fn render(
) {
let updates = displayables
.iter()
.filter(|(pos, _)| pos.pos().distance_squared(player_pos) <= RENDER_DISTANCE_SQ)
.map(|(pos, Display(display))| (pos.pos() - player_pos, *display))
.filter(|(pos, _)| pos.0.distance_squared(player_pos) <= RENDER_DISTANCE_SQ)
.map(|(pos, Display(display))| (pos.0 - player_pos, *display))
.collect();
net.send_message(id, MapUpdates(updates)).ok();
}
fn rerender_players(players: Query<&Pos, With<Player>>, mut writer: EventWriter<RenderUpdate>) {
for Pos(pos) in &players {
writer.send(RenderUpdate(pos.as_ivec2()));
writer.send(RenderUpdate(*pos));
}
}

View file

@ -1,41 +1,94 @@
use bevy::{prelude::*, utils::HashSet};
use super::physics::Pos;
use super::{
display::RenderUpdate,
map::{self, spawn_block},
physics::Pos,
};
pub struct GenerationPlugin;
impl Plugin for GenerationPlugin {
fn build(&self, app: &mut bevy::prelude::App) {
app.insert_resource(GeneratedRegistry::default())
// .add_systems(schedule, systems)
;
.add_systems(Update, chunk_loading);
}
}
fn chunk_loading(cmd: &mut Commands, loaders: Query<&Pos, With<Loader>>, reg: ResMut<GeneratedRegistry>) {
for pos in &loaders {
chunks_surrounding(&pos.pos());
fn chunk_loading(
mut cmd: Commands,
loaders: Query<&Pos, With<Loader>>,
mut reg: ResMut<GeneratedRegistry>,
mut updater: EventWriter<RenderUpdate>,
) {
for loader in &loaders {
for pos in chunks_surrounding(&loader.0, GEN_DISTANCE) {
reg.load_chunk(&mut cmd, &mut updater, pos);
}
}
}
const GEN_DISTANCE: i32 = 1;
const CHUNK_WIDTH: i32 = 8;
fn chunks_surrounding(pos: &IVec2) -> Vec<IVec2> {
todo!()
}
fn snap(value: i32, increment: i32) -> i32 {
todo!()
fn chunks_surrounding(pos: &IVec2, radius: i32) -> Vec<IVec2> {
let center_chunk = pos.div_euclid(IVec2::splat(CHUNK_WIDTH));
(-radius..=radius)
.flat_map(move |x| (-radius..=radius).map(move |y| center_chunk + IVec2::new(x, y)))
.collect()
}
#[test]
fn test_snap() {
// assert_eq!();
fn test_euclid() {
assert_eq!((5_i32).div_euclid(2), 2);
assert_eq!((4_i32).div_euclid(2), 2);
assert_eq!((3_i32).div_euclid(2), 1);
assert_eq!((2_i32).div_euclid(2), 1);
assert_eq!((1_i32).div_euclid(2), 0);
assert_eq!((0_i32).div_euclid(2), 0);
assert_eq!((-1_i32).div_euclid(2), -1);
assert_eq!((-2_i32).div_euclid(2), -1);
assert_eq!((-3_i32).div_euclid(2), -2);
assert_eq!((-4_i32).div_euclid(2), -2);
assert_eq!((-5_i32).div_euclid(2), -3);
}
#[test]
fn test_snap_mod() {
assert_eq!((5_i32).rem_euclid(2), 1);
assert_eq!((4_i32).rem_euclid(2), 0);
assert_eq!((3_i32).rem_euclid(2), 1);
assert_eq!((2_i32).rem_euclid(2), 0);
assert_eq!((1_i32).rem_euclid(2), 1);
assert_eq!((0_i32).rem_euclid(2), 0);
assert_eq!((-1_i32).rem_euclid(2), 1);
assert_eq!((-2_i32).rem_euclid(2), 0);
assert_eq!((-3_i32).rem_euclid(2), 1);
assert_eq!((-4_i32).rem_euclid(2), 0);
assert_eq!((-5_i32).rem_euclid(2), 1);
}
#[derive(Debug, Resource, Default)]
pub struct GeneratedRegistry(HashSet<IVec2>);
impl GeneratedRegistry {
pub fn load_chunk(&mut self, cmd: &mut Commands, rerender: &mut EventWriter<RenderUpdate>, chunk: IVec2) {
if self.0.contains(&chunk) {
return;
}
println!("Loading chunk indexed at position {chunk:?}.");
let from = chunk * CHUNK_WIDTH;
let to = from + IVec2::splat(CHUNK_WIDTH);
for (pos, spawnable) in generate(from, to) {
match spawnable {
Spawnable::Block(display, mass) => spawn_block(cmd, rerender, pos, display, mass),
}
}
self.0.insert(chunk);
}
}
#[derive(Debug, Component)]
pub struct Loader;
@ -44,3 +97,19 @@ pub struct Generated;
#[derive(Debug, Component)]
pub struct Chunk(IVec2);
enum Spawnable {
Block((char, char), usize),
}
fn generate(from: IVec2, _to: IVec2) -> Vec<(IVec2, Spawnable)> {
let structure = "
XX XX
XX XX
";
map::str_to_struct(structure)
.into_iter()
.map(|(pos, display, mass)| (pos + from, Spawnable::Block(display, mass)))
.collect()
}

View file

@ -4,15 +4,22 @@ use bevy::prelude::*;
use itermore::IterArrayChunks;
use super::{
display::Display,
physics::{Mass, Pos},
display::{Display, RenderUpdate},
physics::{Forces, Mass, Pos},
};
fn spawn_block(cmd: &mut Commands, pos: IVec2, display: (char, char), mass: usize) {
cmd.spawn((Pos(pos.as_vec2()), Mass(mass), Display(display)));
pub fn spawn_block(
cmd: &mut Commands,
rerender: &mut EventWriter<RenderUpdate>,
pos: IVec2,
display: (char, char),
mass: usize,
) {
cmd.spawn((Forces::zero(), Pos(pos), Mass(mass), Display(display)));
rerender.send(RenderUpdate(pos));
}
fn str_to_struct(text: &str) -> Vec<(IVec2, (char, char), usize)> {
pub fn str_to_struct(text: &str) -> Vec<(IVec2, (char, char), usize)> {
text.lines()
.enumerate()
.flat_map(|(y, line)| {
@ -25,17 +32,17 @@ fn str_to_struct(text: &str) -> Vec<(IVec2, (char, char), usize)> {
.collect()
}
fn mass_of(tiles: (char, char)) -> usize {
pub fn mass_of(tiles: (char, char)) -> usize {
match tiles {
('#', '#') => 100,
('X', 'X') => 10,
('[', ']') => 5,
('(', ')') => 1,
('[', ']') => 8,
('(', ')') => 5,
_ => 20,
}
}
pub fn spawn_debug_map(mut cmd: Commands) {
pub fn spawn_debug_map(mut cmd: Commands, mut rerender: EventWriter<RenderUpdate>) {
let raw = "
()
@ -49,6 +56,6 @@ pub fn spawn_debug_map(mut cmd: Commands) {
";
for (pos, display, mass) in str_to_struct(raw) {
spawn_block(&mut cmd, pos, display, mass);
spawn_block(&mut cmd, &mut rerender, pos, display, mass);
}
}

View file

@ -16,12 +16,12 @@ impl Plugin for MetricsPlugin {
}
}
fn show_metrics(time: Res<Time>, mut sys: ResMut<SysRes>, players: Query<(), With<Player>>) {
sys.0.refresh_all();
let cpu = sys.0.global_cpu_usage();
fn show_metrics(time: Res<Time>, mut sys: ResMut<SysRes>, players: Query<(), With<Player>>, entities: Query<Entity>) {
let player_count = players.iter().count();
let entity_count = entities.iter().count();
let time_delta = time.delta_seconds();
let cpu = sys.0.global_cpu_usage();
sys.0.refresh_all();
let up = cursor::Up(1);
println!(" | players {player_count:2} | tick {time_delta:2.3} | cpu {cpu:2.1}{up} ");
println!(" | ent {entity_count:2} | ply {player_count:2} | tick {time_delta:2.3} | cpu {cpu:2.1}{up} ");
}

View file

@ -61,7 +61,7 @@ fn respond_greet(
net.send_message(source, ServerGreeting).ok();
for (pos, player) in &players {
if player.0 == source {
render_update.send(RenderUpdate(pos.pos()));
render_update.send(RenderUpdate(pos.0));
}
}
}

View file

@ -6,92 +6,103 @@ pub struct PhysicsPlugin;
impl Plugin for PhysicsPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(
update_physics_forces.after(player::handle_move),
update_physics_center.after(update_physics_forces),
update_physics_collisions.after(update_physics_center),
),
);
app.add_systems(Update, update_physics_forces.after(player::handle_move))
.add_systems(Update, update_physics_pos.after(update_physics_forces));
}
}
#[derive(Debug, Component, Clone)]
pub struct Pos(pub Vec2);
impl Pos {
pub fn fpos(&self) -> Vec2 {
self.0
}
pub fn pos_center(&self) -> Vec2 {
self.pos().as_vec2()
}
pub fn pos(&self) -> IVec2 {
self.0.round().as_ivec2()
}
pub fn dir_to_center(&self) -> Vec2 {
self.pos_center() - self.fpos()
}
}
pub struct Pos(pub IVec2);
#[derive(Debug, Component)]
pub struct Mass(pub usize);
#[derive(Debug, Component)]
pub struct Forces(Vec2);
pub struct Forces(pub IVec2);
impl Forces {
pub fn zero() -> Self {
Self::new(Vec2::ZERO)
Self::new(IVec2::ZERO)
}
pub fn new(forces: Vec2) -> Self {
pub fn new(forces: IVec2) -> Self {
Self(forces)
}
pub fn add(&mut self, force: Vec2) {
pub fn add(&mut self, force: IVec2) {
self.0 += force;
}
pub fn movement(&self, mass: usize) -> IVec2 {
self.0 / mass as i32
}
pub fn movement_step(&self, mass: usize) -> IVec2 {
self.movement(mass).clamp(IVec2::new(-1, -1), IVec2::new(1, 1))
}
}
const MAX_MOVEMENT_PER_TICK: f32 = 0.3;
// 1. reduce force of adjascent objects.
pub fn update_physics_forces(mut forces: Query<(&mut Forces, &Pos, &Mass)>) {
let mut combinations = forces.iter_combinations_mut();
while let Some([left, right]) = combinations.fetch_next() {
let (mut l_forces, l_pos, Mass(l_mass)) = left;
let (mut r_forces, r_pos, Mass(r_mass)) = right;
let l_step = l_forces.movement_step(*l_mass);
let r_step = r_forces.movement_step(*r_mass);
let (l_mass, r_mass) = (*l_mass as i32, *r_mass as i32);
let same_step_x = r_step.x == l_step.x;
let same_step_y = r_step.y == l_step.y;
fn update_physics_forces(mut blocks: Query<(&mut Pos, &mut Forces)>, mut updater: EventWriter<RenderUpdate>) {
for (mut pos, mut forces) in &mut blocks {
let old_pos = pos.clone();
if l_pos.0.distance_squared(r_pos.0) > 2 {
continue;
}
let applied = forces.0;
let applied = applied.clamp_length_max(MAX_MOVEMENT_PER_TICK);
pos.0 += applied;
forces.0 -= applied;
if !same_step_x {
let l_will_overlap_x = l_pos.0 + IVec2::X * l_step.x == r_pos.0;
let r_will_overlap_x = r_pos.0 + IVec2::X * r_step.x == l_pos.0;
if old_pos.pos() != pos.pos() {
updater.send(RenderUpdate(old_pos.pos()));
if l_will_overlap_x {
r_forces.0.x += l_step.x * l_mass;
l_forces.0.x -= l_step.x * r_mass.min(l_mass);
}
if r_will_overlap_x {
l_forces.0.x += r_step.x * r_mass;
r_forces.0.x -= r_step.x * l_mass.min(r_mass);
}
}
if !same_step_y {
let l_will_overlap_y = l_pos.0 + IVec2::Y * l_step.y == r_pos.0;
let r_will_overlap_y = r_pos.0 + IVec2::Y * r_step.y == l_pos.0;
if l_will_overlap_y {
r_forces.0.y += l_step.y * l_mass;
l_forces.0.y -= l_step.y * r_mass.min(l_mass);
}
if r_will_overlap_y {
l_forces.0.y += r_step.y * r_mass;
r_forces.0.y -= r_step.y * l_mass.min(r_mass);
}
}
}
}
pub fn update_physics_collisions(mut blocks: Query<(&mut Pos, &Mass)>) {
let mut combinations = blocks.iter_combinations_mut();
while let Some([(mut a_pos, Mass(a_mass)), (mut b_pos, Mass(b_mass))]) = combinations.fetch_next() {
if a_pos.pos() == b_pos.pos() {
let a_to_b = (b_pos.fpos() - a_pos.fpos()).normalize_or(Vec2::Y);
let total_mass = (a_mass + b_mass) as f32;
let a_mass_frac = (*a_mass) as f32 / total_mass;
let b_mass_frac = (*b_mass) as f32 / total_mass;
let a_movement = (-a_to_b) * b_mass_frac;
let b_movement = (a_to_b) * a_mass_frac;
a_pos.0 += a_movement;
b_pos.0 += b_movement;
}
}
}
// 2. apply 1 step of forces.
pub fn update_physics_center(mut blocks: Query<(&Pos, &mut Forces)>) {
for (pos, mut forces) in &mut blocks {
let dir = pos.pos_center() - pos.0;
forces.add(dir * 0.75);
pub fn update_physics_pos(mut bodies: Query<(&mut Forces, &mut Pos, &Mass)>, mut updater: EventWriter<RenderUpdate>) {
for (mut forces, mut pos, Mass(mass)) in &mut bodies {
let step = forces.movement_step(*mass);
if step == IVec2::ZERO {
continue;
}
updater.send(RenderUpdate(pos.0));
pos.0 += step;
forces.0 -= step * *mass as i32;
updater.send(RenderUpdate(pos.0));
}
}

View file

@ -4,6 +4,7 @@ use bevy_eventwork::{tcp::TcpProvider, AppNetworkMessage, ConnectionId, NetworkD
use super::{
display::{Display, RenderUpdate},
generation::Loader,
physics::{Forces, Mass, Pos},
};
@ -32,14 +33,17 @@ fn player_display_for_dir(dir: IVec2) -> (char, char) {
}
}
const PLAYER_MASS: usize = 10;
pub fn spawn_player(cmd: &mut Commands, conn_id: ConnectionId, pos: IVec2) {
cmd.spawn((
Player(conn_id),
Pos(pos.as_vec2()),
Loader,
Pos(pos),
Display(player_display_for_dir(IVec2::X)),
Dir(IVec2::X),
Forces::zero(),
Mass(10),
Mass(PLAYER_MASS),
));
}
@ -52,21 +56,18 @@ pub fn remove_player(cmd: &mut Commands, conn_id: ConnectionId, players: &Query<
}
pub fn handle_move(
mut messages: EventReader<NetworkData<Move>>,
mut players: Query<(&Player, &mut Forces, &Pos, &mut Dir, &mut Display)>,
mut moves: EventReader<NetworkData<Move>>,
mut players: Query<(&Player, &mut Forces, &mut Dir, &mut Display)>,
) {
for message in messages.read() {
let conn_id = message.source();
for (Player(id), mut forces, pos, mut dir, mut display) in &mut players {
for move_ in moves.read() {
let conn_id = move_.source();
for (Player(id), mut forces, mut dir, mut display) in &mut players {
if conn_id == id {
let dir_movement = message.0.as_vec2().normalize_or_zero();
let added_force = dir_movement + pos.dir_to_center();
forces.add(added_force);
let idir = dir_movement.as_ivec2();
if !(idir == IVec2::ZERO) {
dir.0 = idir;
display.0 = player_display_for_dir(idir);
let mov_dir = move_.0.clamp(IVec2::splat(-1), IVec2::splat(1));
forces.0 = mov_dir * PLAYER_MASS as i32;
if !(mov_dir == IVec2::ZERO) {
dir.0 = mov_dir;
display.0 = player_display_for_dir(mov_dir);
}
}
}