aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorArne Dußin2021-01-27 14:01:50 +0100
committerArne Dußin2021-02-02 22:16:15 +0100
commitf92e9f6f07b1e3834c2ca58ce3510734819d08e4 (patch)
tree20e3d3afce342a56ae98f6c20491482ccd2b5c6b /src
parentc60a6d07efb120724b308e29e8e70f27c87c952d (diff)
downloadgraf_karto-f92e9f6f07b1e3834c2ca58ce3510734819d08e4.tar.gz
graf_karto-f92e9f6f07b1e3834c2ca58ce3510734819d08e4.zip
Rework graf karto to fit the client/server structure
Diffstat (limited to 'src')
-rw-r--r--src/client/cli/cmd/edit.rs (renamed from src/cli/cmd/edit.rs)4
-rw-r--r--src/client/cli/cmd/mod.rs (renamed from src/cli/cmd/mod.rs)2
-rw-r--r--src/client/cli/cmd/read.rs (renamed from src/cli/cmd/read.rs)2
-rw-r--r--src/client/cli/cmd/write.rs (renamed from src/cli/cmd/write.rs)3
-rw-r--r--src/client/cli/mod.rs (renamed from src/cli/mod.rs)6
-rw-r--r--src/client/colours.rs (renamed from src/colours.rs)0
-rw-r--r--src/client/config.rs (renamed from src/config.rs)2
-rw-r--r--src/client/editor.rs (renamed from src/editor.rs)41
-rw-r--r--src/client/grid.rs (renamed from src/grid.rs)4
-rw-r--r--src/client/gui/decimal_num_box.rs (renamed from src/gui/decimal_num_box.rs)0
-rw-r--r--src/client/gui/dimension_indicator.rs (renamed from src/gui/dimension_indicator.rs)32
-rw-r--r--src/client/gui/mod.rs (renamed from src/gui/mod.rs)0
-rw-r--r--src/client/gui/position_indicator.rs (renamed from src/gui/position_indicator.rs)6
-rw-r--r--src/client/gui/tool_sidebar.rs (renamed from src/gui/tool_sidebar.rs)6
-rw-r--r--src/client/input/binding.rs (renamed from src/input/binding.rs)0
-rw-r--r--src/client/input/button.rs (renamed from src/input/button.rs)0
-rw-r--r--src/client/input/mod.rs (renamed from src/input/mod.rs)0
-rw-r--r--src/client/map/icon_mark.rs95
-rw-r--r--src/client/map/icon_texture_manager.rs (renamed from src/map/icon_renderer.rs)14
-rw-r--r--src/client/map/mappable.rs (renamed from src/map/mappable.rs)20
-rw-r--r--src/client/map/mod.rs206
-rw-r--r--src/client/map/room_mark.rs (renamed from src/map/room.rs)61
-rw-r--r--src/client/map/wall_mark.rs (renamed from src/map/wall.rs)63
-rw-r--r--src/client/mod.rs137
-rw-r--r--src/client/snapping.rs (renamed from src/snapping.rs)2
-rw-r--r--src/client/svg/mod.rs (renamed from src/svg/mod.rs)0
-rw-r--r--src/client/svg/style.rs (renamed from src/svg/style.rs)0
-rw-r--r--src/client/tool/deletion_tool.rs (renamed from src/tool/deletion_tool.rs)25
-rw-r--r--src/client/tool/icon_tool.rs (renamed from src/tool/icon_tool.rs)44
-rw-r--r--src/client/tool/mod.rs (renamed from src/tool/mod.rs)28
-rw-r--r--src/client/tool/polygon_room_tool.rs (renamed from src/tool/polygon_room_tool.rs)27
-rw-r--r--src/client/tool/rect_room_tool.rs (renamed from src/tool/rect_room_tool.rs)25
-rw-r--r--src/client/tool/selection_tool.rs (renamed from src/tool/selection_tool.rs)18
-rw-r--r--src/client/tool/wall_tool.rs (renamed from src/tool/wall_tool.rs)15
-rw-r--r--src/client/transform.rs (renamed from src/transform.rs)0
-rw-r--r--src/client_main.rs178
-rw-r--r--src/map/data.rs87
-rw-r--r--src/map/icon.rs102
-rw-r--r--src/map/mod.rs171
-rw-r--r--src/net/cargo.rs33
-rw-r--r--src/net/client/connection.rs119
-rw-r--r--src/net/client/mod.rs4
-rw-r--r--src/net/mod.rs11
-rw-r--r--src/net/packet.rs73
-rw-r--r--src/net/server/connection.rs84
-rw-r--r--src/net/server/connection_manager.rs109
-rw-r--r--src/net/server/mod.rs6
-rw-r--r--src/server/mod.rs48
-rw-r--r--src/server_main.rs65
-rw-r--r--src/stable_vec.rs80
-rw-r--r--src/world/component.rs23
-rw-r--r--src/world/icon.rs23
-rw-r--r--src/world/mod.rs113
-rw-r--r--src/world/room.rs52
-rw-r--r--src/world/wall.rs52
55 files changed, 1636 insertions, 685 deletions
diff --git a/src/cli/cmd/edit.rs b/src/client/cli/cmd/edit.rs
index b164332..7a02959 100644
--- a/src/cli/cmd/edit.rs
+++ b/src/client/cli/cmd/edit.rs
@@ -2,8 +2,8 @@
use super::Command;
use super::{CmdParseError, FromArgs};
-use crate::map::MapData;
-use crate::Editor;
+use crate::client::map::MapData;
+use crate::client::Editor;
use std::path::PathBuf;
/// Command to load a file from the disk and replace the current editor contents with it's info.
diff --git a/src/cli/cmd/mod.rs b/src/client/cli/cmd/mod.rs
index e00b895..b403772 100644
--- a/src/cli/cmd/mod.rs
+++ b/src/client/cli/cmd/mod.rs
@@ -8,7 +8,7 @@ pub use edit::*;
pub use read::*;
pub use write::*;
-use crate::Editor;
+use crate::client::Editor;
use std::ops::RangeInclusive;
/// Errors that can occur when parsing a command. This is for syntax checking, the
diff --git a/src/cli/cmd/read.rs b/src/client/cli/cmd/read.rs
index 45cdf99..313530a 100644
--- a/src/cli/cmd/read.rs
+++ b/src/client/cli/cmd/read.rs
@@ -2,8 +2,8 @@
use super::Command;
use super::{CmdParseError, FromArgs};
+use crate::client::Editor;
use crate::map::MapData;
-use crate::Editor;
use std::path::PathBuf;
/// Command to read a file from the system
diff --git a/src/cli/cmd/write.rs b/src/client/cli/cmd/write.rs
index 399045c..3114f63 100644
--- a/src/cli/cmd/write.rs
+++ b/src/client/cli/cmd/write.rs
@@ -2,8 +2,7 @@
use super::Command;
use super::{CmdParseError, FromArgs};
-use crate::map::MapData;
-use crate::Editor;
+use crate::client::Editor;
use std::path::PathBuf;
/// The save command can take any destination in the filesystem the user can write to. Processing
diff --git a/src/cli/mod.rs b/src/client/cli/mod.rs
index 370a30b..ed2a1bf 100644
--- a/src/cli/mod.rs
+++ b/src/client/cli/mod.rs
@@ -11,10 +11,10 @@
pub mod cmd;
pub use self::cmd::*;
-use crate::colours::DEFAULT_COLOURS;
-use crate::input::{Button, Input};
+use crate::client::colours::DEFAULT_COLOURS;
+use crate::client::input::{Button, Input};
+use crate::client::Editor;
use crate::math::Vec2;
-use crate::Editor;
use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
use std::sync::mpsc::Receiver;
diff --git a/src/colours.rs b/src/client/colours.rs
index d7c728c..d7c728c 100644
--- a/src/colours.rs
+++ b/src/client/colours.rs
diff --git a/src/config.rs b/src/client/config.rs
index 6d0680c..96ff3f5 100644
--- a/src/config.rs
+++ b/src/client/config.rs
@@ -1,6 +1,6 @@
//! Home of the user configuratable content of graf karto, like keybindings and (TODO) colours etc.
-use crate::input::{Binding, Button, Input, MouseButton, Scancode};
+use crate::client::input::{Binding, Button, Input, MouseButton, Scancode};
use ron::de::from_reader;
use ron::ser::{to_string_pretty, PrettyConfig};
use serde::{Deserialize, Serialize};
diff --git a/src/editor.rs b/src/client/editor.rs
index 7bf8f5e..0fb5794 100644
--- a/src/editor.rs
+++ b/src/client/editor.rs
@@ -5,12 +5,13 @@
//! currently a difference between things that are being created (inside the editor) and things that
//! are part of the environment (the map).
-use crate::config::Config;
-use crate::input::{Binding, Input};
-use crate::map::Map;
-use crate::snapping::Snapper;
-use crate::tool::*;
-use crate::transform::Transform;
+use crate::client::config::Config;
+use crate::client::input::{Binding, Input};
+use crate::client::map::Map;
+use crate::client::snapping::Snapper;
+use crate::client::tool::*;
+use crate::client::transform::Transform;
+use crate::net::{Cargo, Connection};
use raylib::core::drawing::RaylibDrawHandle;
use raylib::{RaylibHandle, RaylibThread};
use std::collections::HashMap;
@@ -23,12 +24,18 @@ pub struct Editor {
tools: HashMap<ToolType, (Box<dyn Tool>, Binding)>,
active: ToolType,
config: Config,
+ server: Connection<Cargo>,
}
impl Editor {
/// Create a new editor with all tools necessary. There should be only one editor per program
/// instance.
- pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread, config: Config) -> Self {
+ pub fn new(
+ rl: &mut RaylibHandle,
+ rlt: &RaylibThread,
+ config: Config,
+ server: Connection<Cargo>,
+ ) -> Self {
let map = Map::new(rl, rlt);
let mut tools: HashMap<ToolType, (Box<dyn Tool>, Binding)> =
@@ -87,6 +94,7 @@ impl Editor {
tools,
active: ToolType::RectRoomTool,
config,
+ server,
}
}
@@ -142,17 +150,17 @@ impl Editor {
// Handle common keybindings many of the tools have.
if input.poll_global(&self.config.tool_general_binds.place_single) {
- active_tool.place_single(&mut self.map, &snapped_mouse_pos);
+ active_tool.place_single(&mut self.map, &self.server, &snapped_mouse_pos);
}
if input.poll_global(&self.config.tool_general_binds.finish) {
- active_tool.finish(&mut self.map);
+ active_tool.finish(&mut self.map, &self.server);
}
if input.poll_global(&self.config.tool_general_binds.abort) {
active_tool.abort();
}
// Handle custom keybindings in case the tool has any.
- active_tool.handle_custom_bindings(&mut self.map, input);
+ active_tool.handle_custom_bindings(&mut self.map, &self.server, input);
}
/// Draw all tools and in case of the active tool also what is currently being edited by it, if
@@ -167,8 +175,15 @@ impl Editor {
pub fn map(&self) -> &Map {
&self.map
}
- /// Get the world containing all finished elements mutably.
- pub fn map_mut(&mut self) -> &mut Map {
- &mut self.map
+
+ /// Get the server this editor is connected to. Even if the program is executed locally, this will
+ /// return a server, since one must have been started locally.
+ pub fn server(&self) -> &Connection<Cargo> {
+ &self.server
+ }
+
+ /// Get the server this editor is connected to mutably.
+ pub fn server_mut(&mut self) -> &mut Connection<Cargo> {
+ &mut self.server
}
}
diff --git a/src/grid.rs b/src/client/grid.rs
index 9134a49..17d537d 100644
--- a/src/grid.rs
+++ b/src/client/grid.rs
@@ -1,8 +1,8 @@
//! The grid used to divide the map into evenly sized chunks.
-use crate::colours::DEFAULT_COLOURS;
+use crate::client::colours::DEFAULT_COLOURS;
+use crate::client::transform::Transform;
use crate::math;
-use crate::transform::Transform;
use raylib::drawing::RaylibDraw;
/// Draw an infinite grid that can be moved around on the screen and zoomed in and out of.
diff --git a/src/gui/decimal_num_box.rs b/src/client/gui/decimal_num_box.rs
index e9395f7..e9395f7 100644
--- a/src/gui/decimal_num_box.rs
+++ b/src/client/gui/decimal_num_box.rs
diff --git a/src/gui/dimension_indicator.rs b/src/client/gui/dimension_indicator.rs
index 57f5bcc..fb6394a 100644
--- a/src/gui/dimension_indicator.rs
+++ b/src/client/gui/dimension_indicator.rs
@@ -1,10 +1,12 @@
//! An interface element that shows the size of the selected map items and provides a means to
//! manually change the size of them in a precise manner should need be.
-use crate::colours::DEFAULT_COLOURS;
-use crate::map::Map;
+use crate::client::colours::DEFAULT_COLOURS;
+use crate::client::map::Map;
+use crate::client::transform::Transform;
+use crate::client::Editor;
use crate::math::{self, Rect, Vec2};
-use crate::transform::Transform;
+use crate::net::Cargo;
use nalgebra::{Matrix3, Vector2};
use raylib::drawing::RaylibDraw;
use raylib::ffi::KeyboardKey;
@@ -59,10 +61,10 @@ impl DimensionIndicator {
}
/// Update whatever is selected on the map according to the dimension indicator rules and rulers.
- pub fn update(&mut self, map: &mut Map, rl: &mut RaylibHandle) {
+ pub fn update(&mut self, editor: &Editor, rl: &mut RaylibHandle) {
match self.state {
- State::Watching => self.update_watching(map, rl),
- State::Ruling { .. } => self.update_ruling(map, rl),
+ State::Watching => self.update_watching(editor.map(), rl),
+ State::Ruling { .. } => self.update_ruling(editor, rl),
};
}
@@ -74,9 +76,9 @@ impl DimensionIndicator {
* default, otherwise it is adjusted to the size of the combined selection.
*/
let mut selection_exists = false;
- for e in map.elements() {
+ for (_id, e) in map.elements() {
if e.selected() {
- let element_bounds = e.bounding_rect();
+ let element_bounds = e.as_component().bounding_rect();
if selection_exists {
// Adjust the currently detected selection size.
min.x = math::partial_min(min.x, element_bounds.x);
@@ -112,7 +114,7 @@ impl DimensionIndicator {
}
}
- fn update_ruling(&mut self, map: &mut Map, rl: &mut RaylibHandle) {
+ fn update_ruling(&mut self, editor: &Editor, rl: &mut RaylibHandle) {
// Get the currently edited dimension for processing.
let (edited_dim, editing_x) = match &mut self.state {
State::Watching => panic!("Called ruler update when in watching state"),
@@ -183,7 +185,7 @@ impl DimensionIndicator {
Rect::new(self.bounds.x, self.bounds.y, self.bounds.h, dim)
};
- self.set_bounds(map, new_bounds);
+ self.set_bounds(editor, new_bounds);
}
}
}
@@ -194,7 +196,7 @@ impl DimensionIndicator {
/// # Panics
/// If the `bounds` have a negative value for width or height, the dimensions
/// cannot be set and the function will panic.
- pub fn set_bounds(&mut self, map: &mut Map, bounds: Rect<f64>) {
+ pub fn set_bounds(&mut self, editor: &Editor, bounds: Rect<f64>) {
if bounds.w <= 0. || bounds.h <= 0. {
panic!("Cannot set dimensions of elements to zero.");
}
@@ -216,10 +218,12 @@ impl DimensionIndicator {
.append_nonuniform_scaling(&scale)
.append_translation(&Vector2::new(bounds.x, bounds.y));
- for element in map.elements_mut() {
+ for (id, element) in editor.map().elements() {
if element.selected() {
- if let Some(transformable) = element.as_non_rigid_mut() {
- transformable.apply_matrix(&transform);
+ if element.as_component().as_non_rigid().is_some() {
+ editor
+ .server()
+ .send(Cargo::ApplyMatrix((id, transform.clone())));
}
}
}
diff --git a/src/gui/mod.rs b/src/client/gui/mod.rs
index 62173ec..62173ec 100644
--- a/src/gui/mod.rs
+++ b/src/client/gui/mod.rs
diff --git a/src/gui/position_indicator.rs b/src/client/gui/position_indicator.rs
index 737a427..4d68b86 100644
--- a/src/gui/position_indicator.rs
+++ b/src/client/gui/position_indicator.rs
@@ -6,10 +6,10 @@
//! is a simple HUD so it doesn't interact with anything in the world, but that may change in the
//! future.
-use crate::colours::DEFAULT_COLOURS;
+use crate::client::colours::DEFAULT_COLOURS;
+use crate::client::snapping::Snapper;
+use crate::client::transform::Transform;
use crate::math::Vec2;
-use crate::snapping::Snapper;
-use crate::transform::Transform;
use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
/// Function to draw a dot at the mouse position and the coordinates associated with it.
diff --git a/src/gui/tool_sidebar.rs b/src/client/gui/tool_sidebar.rs
index af6af74..3147bf8 100644
--- a/src/gui/tool_sidebar.rs
+++ b/src/client/gui/tool_sidebar.rs
@@ -3,10 +3,10 @@
// TODO: Currently, the keyboard shortcuts for tools are handled by the editor, but a lot speaks for
// them being handled by the ToolSidebar instead.
-use crate::input::Input;
+use crate::client::input::Input;
+use crate::client::tool::ToolType;
+use crate::client::Editor;
use crate::math::Rect;
-use crate::tool::ToolType;
-use crate::Editor;
use raylib::core::texture::Texture2D;
use raylib::rgui::RaylibDrawGui;
use raylib::{RaylibHandle, RaylibThread};
diff --git a/src/input/binding.rs b/src/client/input/binding.rs
index 386fb66..386fb66 100644
--- a/src/input/binding.rs
+++ b/src/client/input/binding.rs
diff --git a/src/input/button.rs b/src/client/input/button.rs
index e9ef45e..e9ef45e 100644
--- a/src/input/button.rs
+++ b/src/client/input/button.rs
diff --git a/src/input/mod.rs b/src/client/input/mod.rs
index e8b1821..e8b1821 100644
--- a/src/input/mod.rs
+++ b/src/client/input/mod.rs
diff --git a/src/client/map/icon_mark.rs b/src/client/map/icon_mark.rs
new file mode 100644
index 0000000..39fd554
--- /dev/null
+++ b/src/client/map/icon_mark.rs
@@ -0,0 +1,95 @@
+//! Icon marker on the map. For information about icons see [Icon](crate::)
+
+use super::icon_texture_manager::IconTextureManager;
+use crate::client::colours::DEFAULT_COLOURS;
+use crate::client::map::Mappable;
+use crate::client::transform::Transform;
+use crate::math::Vec2;
+use crate::world::{Component, Icon};
+use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle};
+use std::ops::{Deref, DerefMut};
+use std::rc::Rc;
+
+/// Describes an icon in the world and can be drawn.
+#[derive(Clone)]
+pub struct IconMark {
+ icon: Icon,
+ selected: bool,
+ textures: Rc<IconTextureManager>,
+}
+
+impl IconMark {
+ /// Create a new icon marker. Requires the icon textures that are used to render this icon, as well as all
+ /// the information necessary to describe the icon itself.
+ pub fn new(
+ id: usize,
+ position: Vec2<f64>,
+ rotation: f64,
+ renderer: Rc<IconTextureManager>,
+ ) -> Self {
+ Self::from_icon(
+ Icon {
+ id,
+ position,
+ rotation,
+ },
+ renderer,
+ )
+ }
+
+ /// Like `new()`, but with the icon data bundled into the icon type.
+ pub fn from_icon(icon: Icon, textures: Rc<IconTextureManager>) -> Self {
+ Self {
+ icon,
+ selected: false,
+ textures,
+ }
+ }
+}
+
+impl Mappable for IconMark {
+ fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) {
+ let (texture, info) = self.textures.get(self.id);
+ // Round the position to whole pixels to fix rotation problems.
+ let mut position_px =
+ transform.point_m_to_px(&(self.position - (info.anchor / info.pixels_per_m)));
+ position_px.x = position_px.x.floor();
+ position_px.y = position_px.y.floor();
+ rld.draw_texture_ex(
+ texture,
+ position_px,
+ self.rotation as f32,
+ (transform.pixels_per_m() / info.pixels_per_m) as f32,
+ if self.selected() {
+ DEFAULT_COLOURS.icon_selected
+ } else {
+ DEFAULT_COLOURS.icon_normal
+ },
+ );
+ }
+
+ fn set_selected(&mut self, selected: bool) {
+ self.selected = selected;
+ }
+
+ fn selected(&self) -> bool {
+ self.selected
+ }
+
+ fn as_component(&self) -> &dyn Component {
+ self.deref()
+ }
+}
+
+impl Deref for IconMark {
+ type Target = Icon;
+
+ fn deref(&self) -> &Self::Target {
+ &self.icon
+ }
+}
+impl DerefMut for IconMark {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.icon
+ }
+}
diff --git a/src/map/icon_renderer.rs b/src/client/map/icon_texture_manager.rs
index eef053d..c6b7fea 100644
--- a/src/map/icon_renderer.rs
+++ b/src/client/map/icon_texture_manager.rs
@@ -21,14 +21,14 @@ pub(super) struct IconFileInfo {
}
/// Manager for all icon texture or rendering data.
-pub struct IconRenderer {
+pub struct IconTextureManager {
texture_data: Vec<(Texture2D, IconFileInfo)>,
}
-impl IconRenderer {
- /// Create a new icon renderer. This loads all textures and information for icons that is needed
- /// to draw them to the screen. Usually, there should be only one IconRenderer, or at least one
- /// renderer per directory and program instance.
+impl IconTextureManager {
+ /// Create a new icon manager. This loads all textures and information for icons that is needed
+ /// to draw them to the screen. Usually, there should be only one IconTextureManager, or at least one
+ /// manager per directory and program instance.
pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread) -> Self {
/* Read all available icons from the icon directory. SVGs do not need any special scale
* file, but pixel-based file formats require a RON-file declaring what the scale of the
@@ -75,7 +75,7 @@ impl IconRenderer {
Self { texture_data }
}
- /// Get the data needed to render an icon of type `icon_id`.
+ /// Get the textures needed to render an icon of type `icon_id`.
///
/// # Panics
/// If the `icon_id` does not describe a valid icon (is out of bounds), there is no data to be
@@ -84,7 +84,7 @@ impl IconRenderer {
&self.texture_data[icon_id]
}
- /// The number of icons registered in this icon-renderer.
+ /// The number of icons registered in this texture manager.
pub fn num_icons(&self) -> usize {
self.texture_data.len()
}
diff --git a/src/map/mappable.rs b/src/client/map/mappable.rs
index 7978f50..39e774b 100644
--- a/src/map/mappable.rs
+++ b/src/client/map/mappable.rs
@@ -1,9 +1,8 @@
//! Something that's mappable can be placed on the map and drawn at a specific position. It has a
//! dimension on the map and may be transformable in various ways.
-use crate::math::Rect;
-use crate::transform::Transform;
-use crate::transformable::NonRigidTransformable;
+use crate::client::transform::Transform;
+use crate::world::Component;
use raylib::drawing::RaylibDrawHandle;
/// Anything that can be added to the map or world must implement this trait.
@@ -19,17 +18,6 @@ pub trait Mappable {
/// Get if this item is currently selected.
fn selected(&self) -> bool;
- /// Get the rectangle that contains the mappable object in its entirety without excess.
- fn bounding_rect(&self) -> Rect<f64>;
-
- /// If this mappable item can be transformed in a non-rigid way, a dynamic reference is returned,
- /// otherwise none.
- fn as_non_rigid(&self) -> Option<&dyn NonRigidTransformable> {
- None
- }
-
- /// The same as `as_non_rigid`, but mutably.
- fn as_non_rigid_mut(&mut self) -> Option<&mut dyn NonRigidTransformable> {
- None
- }
+ /// Get this mappable as a world component.
+ fn as_component(&self) -> &dyn Component;
}
diff --git a/src/client/map/mod.rs b/src/client/map/mod.rs
new file mode 100644
index 0000000..eaab72f
--- /dev/null
+++ b/src/client/map/mod.rs
@@ -0,0 +1,206 @@
+//! The map is a visual interpretation of all the items that make up the world.
+//!
+//! It's the main structure that the client uses to interact with the world, since
+//! the world contains all the
+
+pub mod icon_mark;
+pub mod icon_texture_manager;
+pub mod mappable;
+pub mod room_mark;
+pub mod wall_mark;
+
+pub use icon_mark::*;
+pub use mappable::Mappable;
+pub use room_mark::*;
+pub use wall_mark::*;
+
+use crate::client::Transform;
+use crate::stable_vec::StableVec;
+use crate::world::{Room, Wall, World};
+use icon_texture_manager::IconTextureManager;
+use raylib::drawing::RaylibDrawHandle;
+use raylib::{RaylibHandle, RaylibThread};
+use std::rc::Rc;
+
+/// The map containing all map elements that are seen on the screen.
+pub struct Map {
+ rooms: StableVec<RoomMark>,
+ walls: StableVec<WallMark>,
+ icons: StableVec<IconMark>,
+ used_ids: StableVec<()>,
+ icon_renderer: Rc<IconTextureManager>,
+}
+
+impl Map {
+ /// Create a new, empty map/world.
+ pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread) -> Self {
+ Self {
+ rooms: StableVec::new(),
+ walls: StableVec::new(),
+ icons: StableVec::new(),
+ used_ids: StableVec::new(),
+ icon_renderer: Rc::new(IconTextureManager::new(rl, rlt)),
+ }
+ }
+
+ pub fn add_room(&mut self, id: usize, room: Room) -> bool {
+ if self.used_ids.try_insert(id, ()).is_ok() {
+ self.rooms
+ .try_insert(id, RoomMark::from_room(room))
+ .unwrap();
+ true
+ } else {
+ error!("Unable to add room. Id already in use.");
+ false
+ }
+ }
+
+ /// Add a wall with a specific id. May fail if there already is an entity with that id.
+ pub fn add_wall(&mut self, id: usize, wall: Wall) -> bool {
+ if self.used_ids.try_insert(id, ()).is_ok() {
+ /* Check for intersections with any wall that was arleady created so the wall ends can be
+ * rendered properly.
+ */
+ let mut start_intersects = false;
+ let mut end_intersects = false;
+ for (_, wall) in self.walls.iter() {
+ if wall.shape().contains_collinear(wall.shape().start) {
+ start_intersects = true;
+ }
+ if wall.shape().contains_collinear(wall.shape().end) {
+ end_intersects = true;
+ }
+
+ // Currently, additional intersections can be ignored, since it is just a yes-no-question
+ if start_intersects && end_intersects {
+ break;
+ }
+ }
+
+ self.walls
+ .try_insert(
+ id,
+ WallMark::from_wall(wall, start_intersects, end_intersects),
+ )
+ .unwrap();
+ true
+ } else {
+ error!("Unable to add wall. Id already in use.");
+ false
+ }
+ }
+
+ /// Add an icon with a specific id. May fail if there already is an entity with that id.
+ pub fn add_icon(&mut self, id: usize, icon: IconMark) -> bool {
+ if self.used_ids.try_insert(id, ()).is_ok() {
+ self.icons.try_insert(id, icon).unwrap();
+ true
+ } else {
+ error!("Unable to add icon. Id already in use.");
+ false
+ }
+ }
+
+ /// Draw all elements of the map to the screen. This should be called after the background of the
+ /// map has already been drawn.
+ pub fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) {
+ for (_, element) in self.elements() {
+ element.draw(rld, transform);
+ }
+ }
+
+ /// Get the icon-renderer that is currently used to render the icons.
+ pub fn icon_renderer(&self) -> Rc<IconTextureManager> {
+ self.icon_renderer.clone()
+ }
+
+ /// Remove item with the id, if it exists. Returns `true` if an item existed, `false` otherwise.
+ pub fn remove(&mut self, id: usize) -> bool {
+ if self.used_ids.remove(id).is_none() {
+ return false;
+ }
+
+ if self.rooms.remove(id).is_some()
+ || self.walls.remove(id).is_some()
+ || self.icons.remove(id).is_some()
+ {
+ true
+ } else {
+ panic!(
+ "Id {} was still registered, eventhough there was no such entity.",
+ id
+ );
+ }
+ }
+
+ /// Iterator over all elements as objects when an operation needs to go over all elements of the
+ /// map.
+ pub fn elements(&self) -> impl Iterator<Item = (usize, &dyn Mappable)> {
+ self.rooms
+ .iter()
+ .map(|(id, p)| (*id, p as &dyn Mappable))
+ .chain(self.walls.iter().map(|(id, w)| (*id, w as &dyn Mappable)))
+ .chain(self.icons.iter().map(|(id, i)| (*id, i as &dyn Mappable)))
+ }
+
+ /// Iterator over all elements, but the individual elements can be mutated. It is however
+ /// impossible to add or remove elements in this way. For that, use the dedicated functions.
+ pub fn elements_mut(&mut self) -> impl Iterator<Item = (usize, &mut dyn Mappable)> {
+ self.rooms
+ .id_iter_mut()
+ .map(|(id, p)| (id, p as &mut dyn Mappable))
+ .chain(
+ self.walls
+ .id_iter_mut()
+ .map(|(id, w)| (id, w as &mut dyn Mappable)),
+ )
+ .chain(
+ self.icons
+ .id_iter_mut()
+ .map(|(id, i)| (id, i as &mut dyn Mappable)),
+ )
+ }
+
+ /// Get the rooms of this map.
+ pub fn rooms(&self) -> &StableVec<RoomMark> {
+ &self.rooms
+ }
+
+ /// Get the walls of this map.
+ pub fn walls(&self) -> &StableVec<WallMark> {
+ &self.walls
+ }
+
+ /// Get the icons of this map.
+ pub fn icons(&self) -> &StableVec<IconMark> {
+ &self.icons
+ }
+
+ /// Replace the internal map data with the data of the world provided.
+ /// (Load and replace)
+ pub fn set_data(&mut self, world: World) {
+ // Remove all data.
+ self.icons.clear();
+ self.rooms.clear();
+ self.walls.clear();
+
+ // Add all data from the map data.
+ self.add_data(world);
+ }
+
+ /// Add the data provided to the current data on the map. All elements will
+ /// remain, with the additional elements being pushed also. This must be
+ /// used with caution, since the ids of the items will remain unchanged, and
+ /// items with the same id will therefore be ignored and not added.
+ pub fn add_data(&mut self, world: World) {
+ for (id, i) in world.icons().iter() {
+ self.add_icon(*id, IconMark::from_icon(*i, self.icon_renderer.clone()));
+ }
+ for (id, r) in world.rooms().iter() {
+ self.add_room(*id, r.clone());
+ }
+ for (id, w) in world.walls().iter() {
+ self.add_wall(*id, w.clone());
+ }
+ }
+}
diff --git a/src/map/room.rs b/src/client/map/room_mark.rs
index 3050763..a9777fb 100644
--- a/src/map/room.rs
+++ b/src/client/map/room_mark.rs
@@ -2,32 +2,29 @@
//! [Polygon](crate::math::Polygon) can have.
use super::Mappable;
-use crate::colours::DEFAULT_COLOURS;
-use crate::math::{self, Polygon, Rect, Triangle};
-use crate::transform::Transform;
-use crate::transformable::NonRigidTransformable;
-use crate::FLOAT_MARGIN;
-use nalgebra::{Matrix3, Point2};
+use crate::client::colours::DEFAULT_COLOURS;
+use crate::client::transform::Transform;
+use crate::client::FLOAT_MARGIN;
+use crate::math::{self, Triangle};
+use crate::world::{Component, Room};
use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
use std::ops::Deref;
-/// Data type for the Polygon room.
-pub type RoomData = Polygon<f64>;
-
/// A polygon room, which can be placed and modified in the world.
-pub struct Room {
- data: RoomData,
+pub struct RoomMark {
+ room: Room,
// The polygon shape, but in triangles, so the polygon does not have to be triangulated every frame.
triangulated: Vec<Triangle<f64>>,
selected: bool,
}
-impl Room {
+impl RoomMark {
/// Create a room from the given polygon data.
- pub fn from_data(data: RoomData) -> Self {
+ pub fn from_room(room: Room) -> Self {
+ let shape = room.shape().clone();
Self {
- data: data.clone(),
- triangulated: math::triangulate(data, FLOAT_MARGIN),
+ room,
+ triangulated: math::triangulate(shape, FLOAT_MARGIN),
selected: false,
}
}
@@ -36,11 +33,11 @@ impl Room {
* properly, so this function must be called any time that happens.
*/
fn retriangulate(&mut self) {
- self.triangulated = math::triangulate(self.data.clone(), FLOAT_MARGIN);
+ self.triangulated = math::triangulate(self.room.shape().clone(), FLOAT_MARGIN);
}
}
-impl Mappable for Room {
+impl Mappable for RoomMark {
fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) {
for triangle in &self.triangulated {
rld.draw_triangle(
@@ -64,35 +61,15 @@ impl Mappable for Room {
self.selected
}
- fn bounding_rect(&self) -> Rect<f64> {
- Rect::bounding_rect_n(&self.data.corners())
- }
-
- fn as_non_rigid(&self) -> Option<&dyn NonRigidTransformable> {
- Some(self as &dyn NonRigidTransformable)
- }
-
- fn as_non_rigid_mut(&mut self) -> Option<&mut dyn NonRigidTransformable> {
- Some(self as &mut dyn NonRigidTransformable)
- }
-}
-
-impl NonRigidTransformable for Room {
- fn apply_matrix(&mut self, matrix: &Matrix3<f64>) {
- for corner in self.data.corners_mut() {
- *corner = matrix
- .transform_point(&Point2::new(corner.x, corner.y))
- .into();
- }
-
- self.retriangulate();
+ fn as_component(&self) -> &dyn Component {
+ self.deref()
}
}
-impl Deref for Room {
- type Target = RoomData;
+impl Deref for RoomMark {
+ type Target = Room;
fn deref(&self) -> &Self::Target {
- &self.data
+ &self.room
}
}
diff --git a/src/map/wall.rs b/src/client/map/wall_mark.rs
index f1748bb..c51af9d 100644
--- a/src/map/wall.rs
+++ b/src/client/map/wall_mark.rs
@@ -1,4 +1,4 @@
-//! Walls, solid barriers that are generally unscaleable.
+//! Walls, solid barriers that are generally unclimbable.
//!
//! This interpretation is generally up to the GM to decide, but generally speaking, a wall cannot be
//! crossed by a player. If special conditions apply (for instance, when the player wants to scale the
@@ -6,30 +6,26 @@
//! a wall.
use super::Mappable;
-use crate::colours::DEFAULT_COLOURS;
-use crate::math::{LineSegment, Rect, Vec2};
-use crate::transform::Transform;
-use crate::transformable::NonRigidTransformable;
-use nalgebra::Matrix3;
+use crate::client::colours::DEFAULT_COLOURS;
+use crate::client::transform::Transform;
+use crate::math::Vec2;
+use crate::world::{Component, Wall};
use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
use std::ops::{Deref, DerefMut};
-/// The data which defines a wall segment completely for serialisation.
-pub type WallData = LineSegment<f64>;
-
/// A solid wall a player cannot go through, except if special conditions apply.
-pub struct Wall {
- data: WallData,
+pub struct WallMark {
+ wall: Wall,
selected: bool,
round_start: bool,
round_end: bool,
}
-impl Wall {
+impl WallMark {
/// Create a new wall from the deserialised data and information known from internal sources.
- pub fn from_data(data: WallData, round_start: bool, round_end: bool) -> Self {
+ pub fn from_wall(wall: Wall, round_start: bool, round_end: bool) -> Self {
Self {
- data,
+ wall,
selected: false,
round_start,
round_end,
@@ -37,8 +33,8 @@ impl Wall {
}
/// Get the internal data for serialisation
- pub fn data(&self) -> &WallData {
- &self.data
+ pub fn data(&self) -> &Wall {
+ &self.wall
}
}
@@ -59,10 +55,10 @@ fn draw_round_corner(
);
}
-impl Mappable for Wall {
+impl Mappable for WallMark {
fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) {
- let start_px = transform.point_m_to_px(&self.data.start);
- let end_px = transform.point_m_to_px(&self.data.end);
+ let start_px = transform.point_m_to_px(&self.wall.shape().start);
+ let end_px = transform.point_m_to_px(&self.wall.shape().end);
rld.draw_line_ex(
start_px,
end_px,
@@ -90,36 +86,21 @@ impl Mappable for Wall {
self.selected
}
- fn bounding_rect(&self) -> Rect<f64> {
- Rect::bounding_rect(self.data.start, self.data.end)
- }
-
- fn as_non_rigid(&self) -> Option<&dyn NonRigidTransformable> {
- Some(self as &dyn NonRigidTransformable)
- }
-
- fn as_non_rigid_mut(&mut self) -> Option<&mut dyn NonRigidTransformable> {
- Some(self as &mut dyn NonRigidTransformable)
- }
-}
-
-impl NonRigidTransformable for Wall {
- fn apply_matrix(&mut self, matrix: &Matrix3<f64>) {
- self.data.start = matrix.transform_point(&self.data.start.into()).into();
- self.data.end = matrix.transform_point(&self.data.end.into()).into();
+ fn as_component(&self) -> &dyn Component {
+ self.deref()
}
}
-impl Deref for Wall {
- type Target = WallData;
+impl Deref for WallMark {
+ type Target = Wall;
fn deref(&self) -> &Self::Target {
- &self.data
+ &self.wall
}
}
-impl DerefMut for Wall {
+impl DerefMut for WallMark {
fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.data
+ &mut self.wall
}
}
diff --git a/src/client/mod.rs b/src/client/mod.rs
new file mode 100644
index 0000000..9151d82
--- /dev/null
+++ b/src/client/mod.rs
@@ -0,0 +1,137 @@
+pub mod cli;
+pub mod colours;
+pub mod config;
+pub mod editor;
+pub mod grid;
+pub mod gui;
+pub mod input;
+pub mod map;
+pub mod snapping;
+pub mod svg;
+pub mod tool;
+pub mod transform;
+
+use crate::net::Connection;
+use cli::CLI;
+use config::Config;
+use editor::Editor;
+use float_cmp::F64Margin;
+use gui::{DimensionIndicator, ToolSidebar};
+use input::Input;
+use raylib::prelude::*;
+use snapping::Snapper;
+use std::ffi::CString;
+use std::io;
+use std::net::{SocketAddr, TcpStream};
+use transform::Transform;
+
+/// Location of the file containing the style used for the raylib user interface.
+pub const GUI_STYLE: &str = "assets/style/cyber.rgs";
+/// Location of the graf karto configuration options file.
+pub const CONFIG_FILE: &str = "config.ron";
+
+/// The acceptable error that is used throughout the project for two floats to be considered equal.
+/// If it is set too low, the editor might not work properly, if set too high, the granularity may
+/// become too low for certain purposes.
+pub const FLOAT_MARGIN: F64Margin = F64Margin {
+ epsilon: 10000. * f64::EPSILON,
+ ulps: 0,
+};
+
+pub fn run(server_address: SocketAddr) {
+ let (mut rl, thread) = raylib::init().resizable().title("Hello there!").build();
+ rl.set_target_fps(120);
+ rl.set_exit_key(None);
+
+ // Load the configuration file, if available.
+ let config = match Config::from_file(CONFIG_FILE) {
+ Ok(config) => config,
+ Err(err) => {
+ /* Create a default config file if it doesn't exist, otherwise leave the incorrectly
+ * formatted/corrupted or otherwise unreadable file alone.
+ */
+ let config = Config::default();
+ if err.kind() == io::ErrorKind::NotFound {
+ warn!("Could not find a configuration file. Creating default.");
+ config
+ .write_file(CONFIG_FILE)
+ .expect("Could not write config file.");
+ } else {
+ error!(
+ "Could not read configuration file: {}\nUsing defaults for this run.",
+ err
+ );
+ }
+
+ config
+ }
+ };
+
+ // Load the preferred gui style
+ rl.gui_load_style(Some(
+ &CString::new(GUI_STYLE).expect("Could not create C string from style file name"),
+ ));
+
+ // Connect to the server at the given address.
+ let server = TcpStream::connect(server_address).expect("Unable to connect to the server.");
+ info!(
+ "Connection to server on `{:?}` established",
+ server.peer_addr()
+ );
+ let server = Connection::new(server);
+
+ let mut input = Input::new(&rl);
+ config::register_bindings(&config, &mut input);
+ let mut editor = Editor::new(&mut rl, &thread, config, server);
+ let mut dimension_indicator = DimensionIndicator::new();
+ let mut tool_sidebar = ToolSidebar::new(&mut rl, &thread, &mut input);
+ let mut snapper = Snapper::default();
+ let mut cli = CLI::new(&mut input);
+
+ let mut transform = Transform::new();
+ let mut last_mouse_pos = rl.get_mouse_position();
+ while !rl.window_should_close() {
+ let screen_width = rl.get_screen_width();
+ let screen_height = rl.get_screen_height();
+
+ input.update(&mut rl);
+
+ // Move the canvas together with the mouse
+ if rl.is_mouse_button_down(MouseButton::MOUSE_MIDDLE_BUTTON) {
+ transform.move_by_px(&(rl.get_mouse_position() - last_mouse_pos).into());
+ }
+ // Update the last mouse position
+ last_mouse_pos = rl.get_mouse_position();
+
+ let mouse_wheel_move = rl.get_mouse_wheel_move();
+ if mouse_wheel_move != 0. {
+ // Zoom in for positive and zoom out for negative mouse wheel rotations.
+ let scale_factor = if mouse_wheel_move > 0. { 1.2 } else { 1. / 1.2 };
+ transform.try_zoom(
+ &rl.get_mouse_position().into(),
+ mouse_wheel_move.abs() as f64 * scale_factor,
+ );
+ }
+
+ cli.update(&mut editor, &mut input);
+ dimension_indicator.update(&mut editor, &mut rl);
+ snapper.update(&mut rl, cli.active());
+ editor.update(&mut rl, &transform, &snapper, &mut input);
+ tool_sidebar.update(screen_height as u16, &mut input);
+
+ // Drawing section
+ {
+ let mut d = rl.begin_drawing(&thread);
+ d.clear_background(Color::BLACK);
+ grid::draw_grid(&mut d, screen_width, screen_height, &transform);
+ editor.map().draw(&mut d, &transform);
+
+ editor.draw_tools(&mut d, &transform);
+ tool_sidebar.draw(&mut d, &mut editor);
+ snapper.draw(&mut d);
+ gui::position_indicator_draw(&mut d, last_mouse_pos.into(), &transform, &snapper);
+ dimension_indicator.draw(&mut d, &transform);
+ cli.draw(&mut d);
+ }
+ }
+}
diff --git a/src/snapping.rs b/src/client/snapping.rs
index ceabf69..b8f9706 100644
--- a/src/snapping.rs
+++ b/src/client/snapping.rs
@@ -5,7 +5,7 @@
//! I thought it should be changeable. This module is responsible for snapping and managing the user
//! instructions telling the program what granularity should currently be used, if any.
-use crate::gui::DecimalNumBox;
+use crate::client::gui::DecimalNumBox;
use crate::math::{self, Vec2};
use raylib::drawing::RaylibDrawHandle;
use raylib::ffi::KeyboardKey;
diff --git a/src/svg/mod.rs b/src/client/svg/mod.rs
index af066f1..af066f1 100644
--- a/src/svg/mod.rs
+++ b/src/client/svg/mod.rs
diff --git a/src/svg/style.rs b/src/client/svg/style.rs
index 7a0110e..7a0110e 100644
--- a/src/svg/style.rs
+++ b/src/client/svg/style.rs
diff --git a/src/tool/deletion_tool.rs b/src/client/tool/deletion_tool.rs
index da2090b..3095ff5 100644
--- a/src/tool/deletion_tool.rs
+++ b/src/client/tool/deletion_tool.rs
@@ -7,10 +7,12 @@
//! and nothing is deleted.
use super::Tool;
-use crate::colours::DEFAULT_COLOURS;
-use crate::map::Map;
+use crate::client::colours::DEFAULT_COLOURS;
+use crate::client::map::Map;
+use crate::client::transform::Transform;
+use crate::client::Connection;
use crate::math::{ExactSurface, Rect, Vec2};
-use crate::transform::Transform;
+use crate::net::Cargo;
use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle};
/// The deletion tool itself.
@@ -29,9 +31,14 @@ impl DeletionTool {
}
}
-fn delete_rect((pos1, pos2): (&Vec2<f64>, &Vec2<f64>), map: &mut Map) {
+fn delete_rect((pos1, pos2): (&Vec2<f64>, &Vec2<f64>), map: &Map, server: &Connection<Cargo>) {
let bounds = Rect::bounding_rect(*pos1, *pos2);
- map.retain(|e| !bounds.contains_rect(&e.bounding_rect()));
+
+ for (id, e) in map.elements() {
+ if bounds.contains_rect(&e.as_component().bounding_rect()) {
+ server.send(Cargo::Remove(id));
+ }
+ }
}
impl Tool for DeletionTool {
@@ -53,18 +60,18 @@ impl Tool for DeletionTool {
}
}
- fn place_single(&mut self, map: &mut Map, mouse_pos_m: &Vec2<f64>) {
+ fn place_single(&mut self, map: &mut Map, server: &Connection<Cargo>, mouse_pos_m: &Vec2<f64>) {
if let Some((pos1, pos2)) = self.deletion_rect {
- delete_rect((&pos1, &pos2), map);
+ delete_rect((&pos1, &pos2), &map, server);
self.deletion_rect = None;
} else {
self.deletion_rect = Some((*mouse_pos_m, *mouse_pos_m));
}
}
- fn finish(&mut self, map: &mut Map) {
+ fn finish(&mut self, map: &mut Map, server: &Connection<Cargo>) {
if let Some((pos1, pos2)) = self.deletion_rect {
- delete_rect((&pos1, &pos2), map);
+ delete_rect((&pos1, &pos2), &map, server);
self.deletion_rect = None;
}
}
diff --git a/src/tool/icon_tool.rs b/src/client/tool/icon_tool.rs
index 8b4afc0..caf9d60 100644
--- a/src/tool/icon_tool.rs
+++ b/src/client/tool/icon_tool.rs
@@ -1,14 +1,16 @@
//! Tool for creating icons. For explanation of icons, please see
//! [the icon module](crate::map::icon).
-use crate::config::IconToolBinds;
-use crate::input::Input;
-use crate::map::icon_renderer::IconRenderer;
-use crate::map::{Icon, Map, Mappable};
+use crate::client::config::IconToolBinds;
+use crate::client::input::Input;
+use crate::client::map::{icon_texture_manager::IconTextureManager, IconMark, Map, Mappable};
+use crate::client::tool::Tool;
+use crate::client::transform::Transform;
use crate::math::Vec2;
-use crate::tool::Tool;
-use crate::transform::Transform;
+use crate::net::Cargo;
+use crate::net::Connection;
use raylib::core::drawing::RaylibDrawHandle;
+use std::ops::Deref;
use std::rc::Rc;
/// The icon tool itself.
@@ -18,19 +20,19 @@ pub struct IconTool {
active: bool,
/// The information of the icon that should be placed / is currently being placed, if it
/// exists.
- current_icon: Icon,
- renderer: Rc<IconRenderer>,
+ current_icon: IconMark,
+ textures: Rc<IconTextureManager>,
}
impl IconTool {
/// Create a new icon tool that renders icons with the provided icon renderer. There should only
/// be one instance of the tool for the program, which should be created in the editor.
- pub fn new(keybindings: IconToolBinds, renderer: Rc<IconRenderer>) -> Self {
+ pub fn new(keybindings: IconToolBinds, textures: Rc<IconTextureManager>) -> Self {
Self {
keybindings,
active: false,
- current_icon: Icon::new(0, Vec2::default(), 0., renderer.clone()),
- renderer,
+ current_icon: IconMark::new(0, Vec2::default(), 0., textures.clone()),
+ textures,
}
}
}
@@ -54,17 +56,27 @@ impl Tool for IconTool {
}
}
- fn place_single(&mut self, map: &mut Map, _mouse_pos_m: &Vec2<f64>) {
- map.push_icon(self.current_icon.clone());
+ fn place_single(
+ &mut self,
+ _map: &mut Map,
+ server: &Connection<Cargo>,
+ _mouse_pos_m: &Vec2<f64>,
+ ) {
+ server.send(Cargo::AddIcon(self.current_icon.deref().clone()));
}
- fn handle_custom_bindings(&mut self, _map: &mut Map, input: &mut Input) {
+ fn handle_custom_bindings(
+ &mut self,
+ _map: &mut Map,
+ _server: &Connection<Cargo>,
+ input: &mut Input,
+ ) {
if input.poll_global(&self.keybindings.next) {
- self.current_icon.id = (self.current_icon.id + 1) % self.renderer.num_icons();
+ self.current_icon.id = (self.current_icon.id + 1) % self.textures.num_icons();
}
if input.poll_global(&self.keybindings.previous) {
self.current_icon.id =
- (self.current_icon.id + self.renderer.num_icons() - 1) % self.renderer.num_icons();
+ (self.current_icon.id + self.textures.num_icons() - 1) % self.textures.num_icons();
}
if input.poll_global(&self.keybindings.rotate_clockwise) {
self.current_icon.rotation += 45.;
diff --git a/src/tool/mod.rs b/src/client/tool/mod.rs
index b3fae80..08e1380 100644
--- a/src/tool/mod.rs
+++ b/src/client/tool/mod.rs
@@ -20,10 +20,12 @@ pub use rect_room_tool::RectRoomTool;
pub use selection_tool::SelectionTool;
pub use wall_tool::WallTool;
-use crate::input::Input;
-use crate::map::Map;
+use crate::client::input::Input;
+use crate::client::map::Map;
+use crate::client::transform::Transform;
+use crate::client::Connection;
use crate::math::Vec2;
-use crate::transform::Transform;
+use crate::net::Cargo;
use raylib::core::drawing::RaylibDrawHandle;
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
@@ -63,18 +65,22 @@ pub trait Tool {
fn update(&mut self, map: &Map, mouse_pos_m: &Vec2<f64>);
/// Draw the contents of this tool.
- // TODO: Maybe make this tool mappable? This might make it easier to make things resizable while
- // it's still being drawn.
fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform);
/// Generic keybinding.
/// Code to place a single node for this tool.
- fn place_single(&mut self, _map: &mut Map, _mouse_pos_m: &Vec2<f64>) {}
+ fn place_single(
+ &mut self,
+ _map: &mut Map,
+ _server: &Connection<Cargo>,
+ _mouse_pos_m: &Vec2<f64>,
+ ) {
+ }
/// Generic keybinding.
/// Code to finish whatever one is doing with this tool currently and trying to apply the
/// changes to the map data.
- fn finish(&mut self, _map: &mut Map) {}
+ fn finish(&mut self, _map: &mut Map, _server: &Connection<Cargo>) {}
/// Generic keybinding.
/// Stop whatever one is doing with this tool and do not apply any changes to the map data.
@@ -82,5 +88,11 @@ pub trait Tool {
/// If there are any additional keybindings that need to be handled by this tool, these can be
/// handled here.
- fn handle_custom_bindings(&mut self, _map: &mut Map, _input: &mut Input) {}
+ fn handle_custom_bindings(
+ &mut self,
+ _map: &mut Map,
+ _server: &Connection<Cargo>,
+ _input: &mut Input,
+ ) {
+ }
}
diff --git a/src/tool/polygon_room_tool.rs b/src/client/tool/polygon_room_tool.rs
index d7c188f..63456cc 100644
--- a/src/tool/polygon_room_tool.rs
+++ b/src/client/tool/polygon_room_tool.rs
@@ -1,11 +1,13 @@
//! Tool to create rooms in the shape of generic polygons.
use super::Tool;
-use crate::colours::DEFAULT_COLOURS;
-use crate::map::Map;
+use crate::client::colours::DEFAULT_COLOURS;
+use crate::client::map::Map;
+use crate::client::transform::Transform;
+use crate::client::FLOAT_MARGIN;
use crate::math::{self, PolygonGraph, Vec2};
-use crate::transform::Transform;
-use crate::FLOAT_MARGIN;
+use crate::net::{Cargo, Connection};
+use crate::world::Room;
use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle};
/// The tool itself.
@@ -29,7 +31,7 @@ impl PolygonRoomTool {
* to the map, clear the currently drawn polygon and return bool. Otherwise it will leave the
* unfinished polygon as is and return false without pushing anything.
*/
- fn try_push(&mut self, map: &mut Map) -> bool {
+ fn try_push(&mut self, server: &Connection<Cargo>) -> bool {
if self.unfinished_room.is_none() {
return false;
}
@@ -43,7 +45,7 @@ impl PolygonRoomTool {
.bounding_polygon(FLOAT_MARGIN)
{
Some(polygon) => {
- map.push_room(polygon);
+ server.send(Cargo::AddRoom(Room::new(polygon)));
self.unfinished_room = None;
true
}
@@ -104,7 +106,12 @@ impl Tool for PolygonRoomTool {
}
}
- fn place_single(&mut self, map: &mut Map, mouse_pos_m: &Vec2<f64>) {
+ fn place_single(
+ &mut self,
+ _map: &mut Map,
+ server: &Connection<Cargo>,
+ mouse_pos_m: &Vec2<f64>,
+ ) {
if let Some((ref mut graph, ref mut last_placed)) = &mut self.unfinished_room {
// If the corner already exists in the polygon, try to finish and push it after adding the
// next edge.
@@ -116,7 +123,7 @@ impl Tool for PolygonRoomTool {
}
if try_finish {
- self.try_push(map);
+ self.try_push(server);
}
} else {
// Start a new unfinished polygon
@@ -124,8 +131,8 @@ impl Tool for PolygonRoomTool {
}
}
- fn finish(&mut self, map: &mut Map) {
- self.try_push(map);
+ fn finish(&mut self, _map: &mut Map, server: &Connection<Cargo>) {
+ self.try_push(server);
}
fn abort(&mut self) {
diff --git a/src/tool/rect_room_tool.rs b/src/client/tool/rect_room_tool.rs
index ec0f0ec..41f2a91 100644
--- a/src/tool/rect_room_tool.rs
+++ b/src/client/tool/rect_room_tool.rs
@@ -3,10 +3,12 @@
//! is necessary and the shape of the room does not have to be very special.
use super::Tool;
-use crate::colours::DEFAULT_COLOURS;
-use crate::map::Map;
+use crate::client::colours::DEFAULT_COLOURS;
+use crate::client::map::Map;
+use crate::client::transform::Transform;
use crate::math::{Rect, Vec2};
-use crate::transform::Transform;
+use crate::net::{Cargo, Connection};
+use crate::world::Room;
use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle};
/// The tool to create simple, rectangular rooms.
@@ -47,7 +49,12 @@ impl Tool for RectRoomTool {
}
}
- fn place_single(&mut self, map: &mut Map, mouse_pos_m: &Vec2<f64>) {
+ fn place_single(
+ &mut self,
+ _map: &mut Map,
+ server: &Connection<Cargo>,
+ mouse_pos_m: &Vec2<f64>,
+ ) {
// Try to finish the rectangle if it has been started.
if let Some((pos1, pos2)) = self.unfinished_rect {
if pos1 == pos2 {
@@ -55,21 +62,25 @@ impl Tool for RectRoomTool {
return;
}
- map.push_room(Rect::bounding_rect(pos1, pos2).into());
+ server.send(Cargo::AddRoom(Room::new(
+ Rect::bounding_rect(pos1, pos2).into(),
+ )));
self.unfinished_rect = None;
} else {
self.unfinished_rect = Some((*mouse_pos_m, *mouse_pos_m));
}
}
- fn finish(&mut self, map: &mut Map) {
+ fn finish(&mut self, _map: &mut Map, server: &Connection<Cargo>) {
if let Some((pos1, pos2)) = self.unfinished_rect {
if pos1 == pos2 {
warn!("Cannot place rectangle with start and endpoint being the same");
return;
}
- map.push_room(Rect::bounding_rect(pos1, pos2).into());
+ server.send(Cargo::AddRoom(Room::new(
+ Rect::bounding_rect(pos1, pos2).into(),
+ )));
self.unfinished_rect = None;
}
}
diff --git a/src/tool/selection_tool.rs b/src/client/tool/selection_tool.rs
index 4850a28..52c2155 100644
--- a/src/tool/selection_tool.rs
+++ b/src/client/tool/selection_tool.rs
@@ -7,10 +7,11 @@
//! For this reason, the selection tool can be thought of as a kind of meta tool over tools.
use super::Tool;
-use crate::colours::DEFAULT_COLOURS;
-use crate::map::Map;
+use crate::client::colours::DEFAULT_COLOURS;
+use crate::client::map::Map;
+use crate::client::transform::Transform;
use crate::math::{ExactSurface, Rect, Vec2};
-use crate::transform::Transform;
+use crate::net::{Cargo, Connection};
use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle};
/// The selection tool makes it possible to select any item on the map when activated.
@@ -48,13 +49,18 @@ impl Tool for SelectionTool {
}
}
- fn place_single(&mut self, map: &mut Map, mouse_pos_m: &Vec2<f64>) {
+ fn place_single(
+ &mut self,
+ map: &mut Map,
+ _server: &Connection<Cargo>,
+ mouse_pos_m: &Vec2<f64>,
+ ) {
if let Some((pos1, pos2)) = self.selection_rect {
// Select all items on the map that are inside of the selection rectangle
let bounds = Rect::bounding_rect(pos1, pos2);
- for element in map.elements_mut() {
+ for (_id, element) in map.elements_mut() {
// TODO: Make it possible to do this additively by custom keybinding.
- element.set_selected(bounds.contains_rect(&element.bounding_rect()));
+ element.set_selected(bounds.contains_rect(&element.as_component().bounding_rect()));
}
self.selection_rect = None;
} else {
diff --git a/src/tool/wall_tool.rs b/src/client/tool/wall_tool.rs
index e79d815..857beea 100644
--- a/src/tool/wall_tool.rs
+++ b/src/client/tool/wall_tool.rs
@@ -2,9 +2,11 @@
//! [the wall module](crate::map::wall).
use super::Tool;
-use crate::map::Map;
+use crate::client::map::Map;
+use crate::client::transform::Transform;
use crate::math::{LineSegment, Vec2};
-use crate::transform::Transform;
+use crate::net::{Cargo, Connection};
+use crate::world::Wall;
use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle};
use raylib::ffi::{Color, Vector2};
@@ -53,11 +55,16 @@ impl Tool for WallTool {
}
}
- fn place_single(&mut self, map: &mut Map, mouse_pos_m: &Vec2<f64>) {
+ fn place_single(
+ &mut self,
+ _map: &mut Map,
+ server: &Connection<Cargo>,
+ mouse_pos_m: &Vec2<f64>,
+ ) {
if let Some(wall) = self.unfinished_wall.take() {
// Continue with the next wall straight away.
self.unfinished_wall = Some(LineSegment::new(wall.end, wall.end));
- map.push_wall(wall);
+ server.send(Cargo::AddWall(Wall::new(wall)));
} else {
self.unfinished_wall = Some(LineSegment::new(*mouse_pos_m, *mouse_pos_m));
}
diff --git a/src/transform.rs b/src/client/transform.rs
index 147956c..147956c 100644
--- a/src/transform.rs
+++ b/src/client/transform.rs
diff --git a/src/client_main.rs b/src/client_main.rs
index 22251c3..e7d1a2d 100644
--- a/src/client_main.rs
+++ b/src/client_main.rs
@@ -21,135 +21,87 @@
#[macro_use]
extern crate log;
-pub mod cli;
-pub mod colours;
-pub mod config;
-pub mod editor;
-pub mod grid;
-pub mod gui;
-pub mod input;
-pub mod map;
+pub mod client;
pub mod math;
-pub mod snapping;
+pub mod net;
+pub mod server;
pub mod stable_vec;
-pub mod svg;
-pub mod tool;
-pub mod transform;
pub mod transformable;
+pub mod world;
-use cli::CLI;
-use config::Config;
-use editor::Editor;
-use float_cmp::F64Margin;
-use gui::{DimensionIndicator, ToolSidebar};
-use input::Input;
-use raylib::prelude::*;
-use snapping::Snapper;
-use std::ffi::CString;
-use std::io;
-use transform::Transform;
-
-/// Location of the file containing the style used for the raylib user interface.
-pub const GUI_STYLE: &str = "assets/style/cyber.rgs";
-/// Location of the graf karto configuration options file.
-pub const CONFIG_FILE: &str = "config.ron";
-
-/// The acceptable error that is used throughout the project for two floats to be considered equal.
-/// If it is set too low, the editor might not work properly, if set too high, the granularity may
-/// become too low for certain purposes.
-pub const FLOAT_MARGIN: F64Margin = F64Margin {
- epsilon: 10000. * f64::EPSILON,
- ulps: 0,
-};
+use clap::{App, Arg};
+use server::DEFAULT_PORT;
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
fn main() {
pretty_env_logger::init();
- let (mut rl, thread) = raylib::init().resizable().title("Hello there!").build();
- rl.set_target_fps(120);
- rl.set_exit_key(None);
-
- // Load the configuration file, if available.
- let config = match Config::from_file(CONFIG_FILE) {
- Ok(config) => config,
- Err(err) => {
- /* Create a default config file if it doesn't exist, otherwise leave the incorrectly
- * formatted/corrupted or otherwise unreadable file alone.
- */
- let config = Config::default();
- if err.kind() == io::ErrorKind::NotFound {
- warn!("Could not find a configuration file. Creating default.");
- config
- .write_file(CONFIG_FILE)
- .expect("Could not write config file.");
- } else {
- error!(
- "Could not read configuration file: {}\nUsing defaults for this run.",
- err
- );
- }
+ println!("Graf Karto version: {}", clap::crate_version!());
+ let default_port = DEFAULT_PORT.to_string();
+ let matches = App::new("Graf Karto")
+ .version(clap::crate_version!())
+ .author(clap::crate_authors!())
+ .about(clap::crate_description!())
+ .arg(Arg::with_name("connect")
+ .short("c")
+ .value_name("SERVER_ADDRESS")
+ .help("Specify an IP in case an external server should be used (starts a local server if not set)."))
+ .arg(Arg::with_name("port")
+ .short("p")
+ .value_name("SERVER_PORT")
+ .help("Set the port the server listens on or should listen on. When starting a local server, others may be tried if it cannot be bound.")
+ .default_value(&default_port))
+ .arg(Arg::with_name("ipv4")
+ .short("v4")
+ .help("Use virgin IPv4 instead of chad IPv6.. you monster"))
+ .get_matches();
- config
+ let use_ipv4 = matches.is_present("ipv4");
+ let mut server_port = match matches
+ .value_of("port")
+ .expect("No port found, eventhough it should have a default value")
+ .parse::<u16>()
+ {
+ Ok(port) => port,
+ Err(e) => {
+ error!("Not a valid server port: {:?}", e);
+ warn!("Using default port {}", DEFAULT_PORT);
+ DEFAULT_PORT
}
};
- // Load the preferred gui style
- rl.gui_load_style(Some(
- &CString::new(GUI_STYLE).expect("Could not create C string from style file name"),
- ));
-
- let mut input = Input::new(&rl);
- config::register_bindings(&config, &mut input);
- let mut editor = Editor::new(&mut rl, &thread, config);
- let mut dimension_indicator = DimensionIndicator::new();
- let mut tool_sidebar = ToolSidebar::new(&mut rl, &thread, &mut input);
- let mut snapper = Snapper::default();
- let mut cli = CLI::new(&mut input);
-
- let mut transform = Transform::new();
- let mut last_mouse_pos = rl.get_mouse_position();
- while !rl.window_should_close() {
- let screen_width = rl.get_screen_width();
- let screen_height = rl.get_screen_height();
-
- input.update(&mut rl);
-
- // Move the canvas together with the mouse
- if rl.is_mouse_button_down(MouseButton::MOUSE_MIDDLE_BUTTON) {
- transform.move_by_px(&(rl.get_mouse_position() - last_mouse_pos).into());
+ let server_address = match matches.value_of("connect") {
+ None => {
+ // Local server will be started.
+ if use_ipv4 {
+ IpAddr::V4(Ipv4Addr::LOCALHOST)
+ } else {
+ IpAddr::V6(Ipv6Addr::LOCALHOST)
+ }
}
- // Update the last mouse position
- last_mouse_pos = rl.get_mouse_position();
-
- let mouse_wheel_move = rl.get_mouse_wheel_move();
- if mouse_wheel_move != 0. {
- // Zoom in for positive and zoom out for negative mouse wheel rotations.
- let scale_factor = if mouse_wheel_move > 0. { 1.2 } else { 1. / 1.2 };
- transform.try_zoom(
- &rl.get_mouse_position().into(),
- mouse_wheel_move.abs() as f64 * scale_factor,
- );
+ Some(addr) => {
+ if use_ipv4 {
+ IpAddr::V4(dbg!(addr).parse().expect("Not a valid IPv4 address"))
+ } else {
+ IpAddr::V6(dbg!(addr).parse().expect("Not a valid IPv6 address"))
+ }
}
+ };
- cli.update(&mut editor, &mut input);
- dimension_indicator.update(editor.map_mut(), &mut rl);
- snapper.update(&mut rl, cli.active());
- editor.update(&mut rl, &transform, &snapper, &mut input);
- tool_sidebar.update(screen_height as u16, &mut input);
+ let server_handle = if !matches.is_present("connect") {
+ Some({
+ let (server_handle, port) =
+ server::start_any_port(use_ipv4).expect("Unable to start local server");
+ server_port = port;
+ server_handle
+ })
+ } else {
+ None
+ };
- // Drawing section
- {
- let mut d = rl.begin_drawing(&thread);
- d.clear_background(Color::BLACK);
- grid::draw_grid(&mut d, screen_width, screen_height, &transform);
- editor.map().draw(&mut d, &transform);
+ client::run(SocketAddr::new(server_address, server_port));
- editor.draw_tools(&mut d, &transform);
- tool_sidebar.draw(&mut d, &mut editor);
- snapper.draw(&mut d);
- gui::position_indicator_draw(&mut d, last_mouse_pos.into(), &transform, &snapper);
- dimension_indicator.draw(&mut d, &transform);
- cli.draw(&mut d);
- }
+ if let Some(handle) = server_handle {
+ handle.join().expect("Server thread closed unexpectedly.");
}
}
diff --git a/src/map/data.rs b/src/map/data.rs
deleted file mode 100644
index 20f193d..0000000
--- a/src/map/data.rs
+++ /dev/null
@@ -1,87 +0,0 @@
-//! Module containing the raw map data version of the map.
-
-use super::{IconData, Map, RoomData, WallData};
-use ron::de::from_reader;
-use ron::ser::{to_string_pretty, PrettyConfig};
-use serde::{Deserialize, Serialize};
-use std::fs::File;
-use std::io::{self, Write};
-use std::path::Path;
-
-/// The serialisable and deserialisable parts of the map. This can be created to get a version of the
-/// map which is persistifiable or sendable/receivable without data overhead or data that might make
-/// it easily corruptable.
-#[derive(Serialize, Deserialize)]
-pub struct MapData {
- pub(super) rooms: Vec<RoomData>,
- pub(super) walls: Vec<WallData>,
- pub(super) icons: Vec<IconData>,
-}
-
-impl MapData {
- /// Create a serialisable map data type from the data elements contained in a map.
- pub fn new(rooms: Vec<RoomData>, walls: Vec<WallData>, icons: Vec<IconData>) -> Self {
- Self {
- rooms,
- walls,
- icons,
- }
- }
-
- /// Creates a data struct from the Map. It is important to note, that this data element is not
- /// bound to the Map in any way after this, so changing anything won't change anything in the map.
- /// It is useful however to for instance serialize this map without extra rendering information
- /// included.
- pub fn extract_data(map: &Map) -> Self {
- Self {
- rooms: map
- .rooms()
- .iter()
- .map(|p| (p as &RoomData).clone())
- .collect(),
- walls: map
- .walls()
- .iter()
- .map(|w| (w as &WallData).clone())
- .collect(),
- icons: map
- .icons()
- .iter()
- .map(|i| (i as &IconData).clone())
- .collect(),
- }
- }
-
- /// Load the map data from a file. Fails if the file does not exist or cannot be correctly parsed.
- pub fn load_from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
- let file = File::open(&path)?;
- let data: Self = match from_reader(file) {
- Ok(data) => data,
- Err(err) => {
- return Err(io::Error::new(io::ErrorKind::InvalidData, err));
- }
- };
-
- Ok(data)
- }
-
- /// Write the map data to the file located at `path`. If the file already exists, it will be
- /// overwritten. If the write fails, an IO-Error is returned.
- pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
- let mut file = File::create(&path)?;
-
- let pretty_conf = PrettyConfig::new()
- .with_depth_limit(4)
- .with_decimal_floats(true)
- .with_separate_tuple_members(true)
- .with_indentor("\t".to_owned());
- let string = match to_string_pretty(&self, pretty_conf) {
- Ok(string) => string,
- Err(err) => {
- return Err(io::Error::new(io::ErrorKind::InvalidInput, err));
- }
- };
-
- file.write_all(&string.as_bytes())
- }
-}
diff --git a/src/map/icon.rs b/src/map/icon.rs
deleted file mode 100644
index 2e45486..0000000
--- a/src/map/icon.rs
+++ /dev/null
@@ -1,102 +0,0 @@
-//! Icons are map elements that have a specific size and cannot be stretched. They are usually used
-//! as markers for specific places in the world.
-
-use super::icon_renderer::IconRenderer;
-use crate::colours::DEFAULT_COLOURS;
-use crate::map::Mappable;
-use crate::math::{Rect, Vec2};
-use crate::transform::Transform;
-use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle};
-use serde::{Deserialize, Serialize};
-use std::ops::{Deref, DerefMut};
-use std::rc::Rc;
-
-/// The icon data necessary to create an Icon again.
-#[derive(Clone, Serialize, Deserialize)]
-pub struct IconData {
- /// The id of the icon is the icons position in the currently loaded icon_data vector.
- pub id: usize,
- /// The position of the icon on the map, given by the vector in meters.
- pub position: Vec2<f64>,
- /// Rotation of the icon texture in degrees.
- pub rotation: f64,
-}
-
-/// Describes an icon in the world and can be drawn.
-#[derive(Clone)]
-pub struct Icon {
- data: IconData,
- selected: bool,
- renderer: Rc<IconRenderer>,
-}
-
-impl Icon {
- /// Create a new icon. Requires the icon renderer that is used to render this icon, as well as all
- /// the information necessary to describe the icon itself.
- pub fn new(id: usize, position: Vec2<f64>, rotation: f64, renderer: Rc<IconRenderer>) -> Self {
- Self::from_data(
- IconData {
- id,
- position,
- rotation,
- },
- renderer,
- )
- }
-
- /// Like `new()`, but with the icon data bundled into the icon data type.
- pub fn from_data(data: IconData, renderer: Rc<IconRenderer>) -> Self {
- Self {
- data,
- selected: false,
- renderer,
- }
- }
-}
-
-impl Mappable for Icon {
- fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) {
- let (texture, info) = self.renderer.get(self.id);
- // Round the position to whole pixels to fix rotation problems.
- let mut position_px =
- transform.point_m_to_px(&(self.position - (info.anchor / info.pixels_per_m)));
- position_px.x = position_px.x.floor();
- position_px.y = position_px.y.floor();
- rld.draw_texture_ex(
- texture,
- position_px,
- self.rotation as f32,
- (transform.pixels_per_m() / info.pixels_per_m) as f32,
- if self.selected() {
- DEFAULT_COLOURS.icon_selected
- } else {
- DEFAULT_COLOURS.icon_normal
- },
- );
- }
-
- fn set_selected(&mut self, selected: bool) {
- self.selected = selected;
- }
-
- fn selected(&self) -> bool {
- self.selected
- }
-
- fn bounding_rect(&self) -> Rect<f64> {
- Rect::new(self.data.position.x, self.data.position.y, 0., 0.)
- }
-}
-
-impl Deref for Icon {
- type Target = IconData;
-
- fn deref(&self) -> &Self::Target {
- &self.data
- }
-}
-impl DerefMut for Icon {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.data
- }
-}
diff --git a/src/map/mod.rs b/src/map/mod.rs
deleted file mode 100644
index e1def09..0000000
--- a/src/map/mod.rs
+++ /dev/null
@@ -1,171 +0,0 @@
-//! The map contains all the items that make up the world.
-//!
-//! There are two main structs to look out for, the first being [Map]. This is the interaction point
-//! for most parts of the program. It contains the actual elements that are drawn on the screen. and
-//! can be changed by the user.
-//! The second is [MapData] and it contains the data that can be saved/loaded and distributed. Every
-//! map item has an internal item that it can be dereferenced to and can be used to construct this
-//! exact item in the same world elsewhere or at a different time. This is often different from the
-//! item that is being drawn. An example would be the [PolygonRoom], which contains a triangulated
-//! version of itself, so it can be drawn without always having to compute the triangles every frame.
-//! It's data type however [PolygonRoomData] contains only the raw polygon data, not the triangulated
-//! version, since that is enough to create the same [PolygonRoom] again.
-
-pub mod data;
-pub mod icon;
-pub mod icon_renderer;
-pub mod mappable;
-pub mod room;
-pub mod wall;
-
-pub use data::MapData;
-pub use icon::*;
-pub use mappable::Mappable;
-pub use room::*;
-pub use wall::*;
-
-use crate::transform::Transform;
-use icon_renderer::IconRenderer;
-use raylib::drawing::RaylibDrawHandle;
-use raylib::{RaylibHandle, RaylibThread};
-use std::rc::Rc;
-
-/// The map containing all map elements that are seen on the screen.
-pub struct Map {
- rooms: Vec<Room>,
- walls: Vec<Wall>,
- icons: Vec<Icon>,
- icon_renderer: Rc<IconRenderer>,
-}
-
-impl Map {
- /// Create a new, empty map/world.
- pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread) -> Self {
- Self {
- rooms: Vec::new(),
- walls: Vec::new(),
- icons: Vec::new(),
- icon_renderer: Rc::new(IconRenderer::new(rl, rlt)),
- }
- }
-
- /// Add a room to the map. Currently, holes are not supported in the polygon, but this might
- /// change later.
- pub fn push_room(&mut self, room_data: RoomData) {
- self.rooms.push(Room::from_data(room_data));
- }
-
- /// Add a wall to the world.
- pub fn push_wall(&mut self, wall_data: WallData) {
- /* Check for intersections with any wall that was arleady created so the wall ends can be
- * rendered properly.
- */
- let mut start_intersects = false;
- let mut end_intersects = false;
- for wall in &self.walls {
- if wall.data().contains_collinear(wall_data.start) {
- start_intersects = true;
- }
- if wall.data().contains_collinear(wall_data.end) {
- end_intersects = true;
- }
-
- // Currently, additional intersections can be ignored, since it is just a yes-no-question
- if start_intersects && end_intersects {
- break;
- }
- }
-
- self.walls
- .push(Wall::from_data(wall_data, start_intersects, end_intersects));
- }
-
- /// Add an icon to the world.
- pub fn push_icon(&mut self, icon: Icon) {
- self.icons.push(icon);
- }
-
- /// Draw all elements of the map to the screen. This should be called after the background of the
- /// map has already been drawn.
- pub fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) {
- for element in self.elements() {
- element.draw(rld, transform);
- }
- }
-
- /// Get the icon-renderer that is currently used to render the icons.
- pub fn icon_renderer(&self) -> Rc<IconRenderer> {
- self.icon_renderer.clone()
- }
-
- /// Retain all map elements that fulfill the given predicate, removing everything else.
- pub fn retain<F>(&mut self, mut f: F)
- where
- F: FnMut(&dyn Mappable) -> bool,
- {
- // Call retain on all vectors containing the maps actual types.
- self.rooms.retain(|p| f(p as &dyn Mappable));
- self.walls.retain(|w| f(w as &dyn Mappable));
- self.icons.retain(|i| f(i as &dyn Mappable));
- }
-
- /// Iterator over all elements as objects when an operation needs to go over all elements of the
- /// map.
- pub fn elements(&self) -> impl Iterator<Item = &dyn Mappable> {
- self.rooms
- .iter()
- .map(|p| p as &dyn Mappable)
- .chain(self.walls.iter().map(|w| w as &dyn Mappable))
- .chain(self.icons.iter().map(|i| i as &dyn Mappable))
- }
-
- /// Iterator over all elements, but the individual elements can be mutated. It is however
- /// impossible to add or remove elements in this way. For that, use the dedicated functions.
- pub fn elements_mut(&mut self) -> impl Iterator<Item = &mut dyn Mappable> {
- self.rooms
- .iter_mut()
- .map(|p| p as &mut dyn Mappable)
- .chain(self.walls.iter_mut().map(|w| w as &mut dyn Mappable))
- .chain(self.icons.iter_mut().map(|i| i as &mut dyn Mappable))
- }
-
- /// Get the rooms of this map.
- pub fn rooms(&self) -> &Vec<Room> {
- &self.rooms
- }
-
- /// Get the walls of this map.
- pub fn walls(&self) -> &Vec<Wall> {
- &self.walls
- }
-
- /// Get the icons of this map.
- pub fn icons(&self) -> &Vec<Icon> {
- &self.icons
- }
-
- /// Replace the internal map data with the data provided. (Load and replace)
- pub fn set_data(&mut self, data: MapData) {
- // Remove all data.
- self.icons.clear();
- self.rooms.clear();
- self.walls.clear();
-
- // Add all data from the map data.
- self.add_data(data);
- }
-
- /// Add the data provided to the current data on the map. All elements will remain, with the
- /// additional elements being pushed also.
- pub fn add_data(&mut self, data: MapData) {
- for i in data.icons {
- self.push_icon(Icon::from_data(i, self.icon_renderer.clone()))
- }
- for p in data.rooms {
- self.push_room(p);
- }
- for w in data.walls {
- self.push_wall(w);
- }
- }
-}
diff --git a/src/net/cargo.rs b/src/net/cargo.rs
new file mode 100644
index 0000000..b05944c
--- /dev/null
+++ b/src/net/cargo.rs
@@ -0,0 +1,33 @@
+//! The cargo is information actually concerning the inner workings of graf karto,
+//! as opposed to the inner workings of the network library.
+
+use crate::world::{Icon, Room, Wall, World};
+use nalgebra::Matrix3;
+use serde::{Deserialize, Serialize};
+
+/// Packets sent oven the network will carry this cargo to inform on what the client needs or the
+/// server wants.
+#[derive(Debug, Deserialize, Serialize)]
+pub enum Cargo {
+ /// Client -> Server: Request to add an icon to the map
+ AddIcon(Icon),
+ /// Client -> Server: Request to add a room to the map
+ AddRoom(Room),
+ /// Client -> Server: Request to add a wall to the map
+ AddWall(Wall),
+ /// Client <-> Server: Update the info of the icon with the given id.
+ UpdateIcon((usize, Icon)),
+ /// Client <-> Server: Update the info of the room with the given id.
+ UpdateRoom((usize, Room)),
+ /// Client <-> Server: Update the info of the wall with the given id.
+ UpdateWall((usize, Wall)),
+ /// Client -> Server: Request to apply the given matrix to the item with the provided id.
+ /// If the matrix cannot be applied to an item with the given id, it will do nothing.
+ ApplyMatrix((usize, Matrix3<f64>)),
+ /// Client <-> Remove the item with the given id.
+ Remove(usize),
+ /// Server -> Client: Add all of the data additively to the map
+ AddMapData(World),
+ /// Server -> Client: Clear the current map data of the client and replace it with this.
+ UpdateMapData(World),
+}
diff --git a/src/net/client/connection.rs b/src/net/client/connection.rs
new file mode 100644
index 0000000..2941a0a
--- /dev/null
+++ b/src/net/client/connection.rs
@@ -0,0 +1,119 @@
+//! A connection to a server using the TCP-Protocol.
+
+use super::super::{Packet, PacketRwError};
+use serde::de::DeserializeOwned;
+use serde::Serialize;
+use std::fmt::Debug;
+use std::io;
+use std::net::TcpStream;
+use std::ops::DerefMut;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::mpsc::{self, Receiver};
+use std::sync::Arc;
+use std::thread::{self, JoinHandle};
+use std::time::Duration;
+use std::{cell::RefCell, net::Shutdown};
+
+/// Represents a connection to the server.
+pub struct Connection<P: 'static + Debug + Send + DeserializeOwned + Serialize> {
+ stream: RefCell<TcpStream>,
+ packet_rx: Receiver<Packet<P>>,
+ running: Arc<AtomicBool>,
+ rcv_thread_handle: Option<JoinHandle<()>>,
+}
+
+impl<P: 'static + Debug + Send + DeserializeOwned + Serialize> Connection<P> {
+ /// Create a new connection based on the given TCP-Stream. Will start up an
+ /// extra thread on which it will receive packets, while the thread with the
+ /// connection can send packets and query for packets that may have been
+ /// received.
+ pub fn new(stream: TcpStream) -> Self {
+ let running = Arc::new(AtomicBool::new(true));
+
+ let (tx, rx) = mpsc::channel();
+ let mut stream_cl = stream
+ .try_clone()
+ .expect("Unable to create tcp stream for receiving packets.");
+ let running_cl = running.clone();
+ let rcv_thread_handle = thread::spawn(move || {
+ // Set the read timeout, so that this thread can be closed gracefully.
+ stream_cl
+ .set_read_timeout(Some(Duration::from_millis(500)))
+ .expect("Unable to set socket read timeout.");
+
+ while running_cl.load(Ordering::Relaxed) {
+ let packet = match Packet::read_from_stream(&mut stream_cl) {
+ Ok(p) => p,
+ Err(PacketRwError::Closed) => Packet::Disconnect,
+ Err(PacketRwError::IOError(err)) => {
+ if err.kind() == io::ErrorKind::WouldBlock {
+ // This error is thrown when the remote reaches the
+ // timeout duration and can thusly be ignored.
+ continue;
+ }
+
+ error!("Unable to read packet: {:?}", err);
+ continue;
+ }
+ Err(err) => {
+ error!("Unable to read packet. {:?}", err);
+ continue;
+ }
+ };
+
+ // Send the packet through the mspc channel.
+ tx.send(packet).unwrap();
+ }
+ });
+
+ Self {
+ stream: RefCell::new(stream),
+ packet_rx: rx,
+ running,
+ rcv_thread_handle: Some(rcv_thread_handle),
+ }
+ }
+
+ /// Get the next packet in case one has been received. If no packet has been
+ /// received since the latest packet has been checked last time, this will
+ /// return none. If the client has been disconnected, this will also return
+ /// `None`.
+ pub fn next_packet(&self) -> Option<P> {
+ match self.packet_rx.try_recv() {
+ Ok(Packet::Disconnect) => {
+ self.running.store(false, Ordering::Relaxed);
+ info!("Server disconnected client.");
+ None
+ }
+ Ok(Packet::Cargo(packet)) => Some(packet),
+ Err(err) => {
+ warn!("unable to receive packet: {}", err);
+ None
+ }
+ }
+ }
+
+ /// Send a packet to the server.
+ pub fn send(&self, packet: P) -> Result<(), PacketRwError> {
+ Packet::Cargo(packet).write_to_stream(self.stream.borrow_mut().deref_mut())
+ }
+
+ /// Stop the client. No further packets will be received or be sent.
+ pub fn stop(&mut self) -> Result<(), io::Error> {
+ self.stream.borrow_mut().shutdown(Shutdown::Both)?;
+ self.running.store(false, Ordering::Relaxed);
+ if let Some(handle) = self.rcv_thread_handle.take() {
+ handle
+ .join()
+ .expect("Packet receive thread closed unexpectedly.");
+ }
+
+ Ok(())
+ }
+}
+
+impl<P: 'static + Debug + Send + DeserializeOwned + Serialize> Drop for Connection<P> {
+ fn drop(&mut self) {
+ self.stop().expect("Failed to stop the client properly");
+ }
+}
diff --git a/src/net/client/mod.rs b/src/net/client/mod.rs
new file mode 100644
index 0000000..d5db3e8
--- /dev/null
+++ b/src/net/client/mod.rs
@@ -0,0 +1,4 @@
+//! Networking items used solely by the client.
+
+pub mod connection;
+pub use connection::*;
diff --git a/src/net/mod.rs b/src/net/mod.rs
new file mode 100644
index 0000000..eb68d1d
--- /dev/null
+++ b/src/net/mod.rs
@@ -0,0 +1,11 @@
+//! Network module containing a simple client/server library.
+
+pub mod cargo;
+pub mod client;
+pub(self) mod packet;
+pub mod server;
+
+pub use cargo::*;
+pub use client::*;
+pub(self) use packet::*;
+pub use server::*;
diff --git a/src/net/packet.rs b/src/net/packet.rs
new file mode 100644
index 0000000..2d97504
--- /dev/null
+++ b/src/net/packet.rs
@@ -0,0 +1,73 @@
+use serde::de::DeserializeOwned;
+use serde::{Deserialize, Serialize};
+use std::fmt::Debug;
+use std::io;
+use std::mem;
+
+#[derive(Debug, Deserialize, Serialize)]
+pub(super) enum Packet<P: 'static + Send + Debug> {
+ Cargo(P),
+ Disconnect,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum PacketRwError {
+ #[error("packet could not be properly deserialised: {0}")]
+ DeserialiseError(bincode::Error),
+ #[error("packet could not be properly serialised: {0}")]
+ SerialiseError(bincode::Error),
+ #[error("unable to read packet from stream: {0}")]
+ IOError(io::Error),
+ #[error("connection was closed from the remote end")]
+ Closed,
+}
+
+impl<P: 'static + Send + Debug + DeserializeOwned + Serialize> Packet<P> {
+ pub fn write_to_stream(&self, stream: &mut impl io::Write) -> Result<(), PacketRwError> {
+ let data: Vec<u8> =
+ bincode::serialize(&self).map_err(|err| PacketRwError::SerialiseError(err))?;
+
+ // Write head with packet length
+ assert!(data.len() as u64 <= u32::MAX as u64);
+ let len = data.len() as u32;
+ let len = bincode::serialize(&len).map_err(|err| PacketRwError::SerialiseError(err))?;
+ stream
+ .write_all(&len)
+ .map_err(|err| PacketRwError::IOError(err))?;
+
+ // Write the data of the packet and pray all errors are caught.
+ Ok(stream
+ .write_all(&data)
+ .map_err(|err| PacketRwError::IOError(err))?)
+ }
+
+ pub fn read_from_stream(stream: &mut impl io::Read) -> Result<Self, PacketRwError> {
+ // Read packet head which informs us of the length.
+ let mut len = vec![0; mem::size_of::<u32>()];
+ stream.read_exact(&mut len).map_err(|err| {
+ if err.kind() == io::ErrorKind::UnexpectedEof {
+ PacketRwError::Closed
+ } else {
+ PacketRwError::IOError(err)
+ }
+ })?;
+ let len: u32 = bincode::deserialize(&len)
+ .expect("Unable to deserialise length of packet. Stream is corrupted.");
+
+ // Read all data from the packet according to the length.
+ let mut data = vec![0; len as usize];
+ match stream.read_exact(&mut data) {
+ Ok(()) => {
+ let res: Result<Self, bincode::Error> = bincode::deserialize(&data);
+ Ok(res.map_err(|err| PacketRwError::DeserialiseError(err))?)
+ }
+ Err(err) => {
+ if err.kind() == io::ErrorKind::UnexpectedEof {
+ Err(PacketRwError::Closed)
+ } else {
+ Err(PacketRwError::IOError(err))
+ }
+ }
+ }
+ }
+}
diff --git a/src/net/server/connection.rs b/src/net/server/connection.rs
new file mode 100644
index 0000000..801eb4b
--- /dev/null
+++ b/src/net/server/connection.rs
@@ -0,0 +1,84 @@
+//! A TCP-connection from a client to the server.
+
+use super::super::packet::{Packet, PacketRwError};
+use super::connection_manager::ConnId;
+use serde::de::DeserializeOwned;
+use serde::Serialize;
+use std::fmt::Debug;
+use std::net::TcpStream;
+use std::sync::mpsc::Sender;
+use std::thread::{self, JoinHandle};
+
+/// Holds a stream to the client and manages receiving packets from said client
+/// in an extra thread.
+pub struct Connection {
+ stream: TcpStream,
+ rcv_thread_handle: JoinHandle<()>,
+}
+
+impl Connection {
+ /// Start up the receiving thread after a client connection has been detected.
+ /// Will create a ghost disconnect packet in case the client disconnects.
+ pub(super) fn start_rcv<P>(
+ conn_id: ConnId,
+ stream: TcpStream,
+ packet_tx: Sender<(ConnId, Packet<P>)>,
+ ) -> Self
+ where
+ P: 'static + Debug + Send + DeserializeOwned + Serialize,
+ {
+ let mut stream_cl = stream
+ .try_clone()
+ .expect("Unable to clone TcpStream handle");
+ let rcv_thread_handle = thread::spawn(move || {
+ let mut running = true;
+ while running {
+ // Read the newest packet from the stream.
+ let packet = match Packet::read_from_stream(&mut stream_cl) {
+ Ok(packet) => dbg!(packet),
+ Err(PacketRwError::Closed) => {
+ // Stop the thread after this packet.
+ running = false;
+
+ /* Generate an internal disconnection packet, so the connection
+ * manager can call cleanup code if necessary.
+ */
+ Packet::Disconnect
+ }
+ Err(err) => {
+ error!(
+ "Receiving packet failed. Connection `{}`. {:?}",
+ conn_id, err
+ );
+
+ // Ignore the received data.
+ continue;
+ }
+ };
+
+ /* Try sending the packet to the Connection manager. If it has already
+ * stopped and hung up on the channel, stop this receive thread as well.
+ */
+ if packet_tx.send((conn_id, packet)).is_err() {
+ info!("Shutting down connection `{}`", conn_id);
+ running = false;
+ }
+ }
+
+ info!("Packet receive thread has stopped running.");
+ });
+
+ Self {
+ stream,
+ rcv_thread_handle,
+ }
+ }
+
+ /// Send a packet to the client via TCP.
+ pub(super) fn send<P>(&mut self, packet: &Packet<P>) -> Result<(), PacketRwError>
+ where
+ P: 'static + Debug + Send + DeserializeOwned + Serialize,
+ {
+ packet.write_to_stream(&mut self.stream)
+ }
+}
diff --git a/src/net/server/connection_manager.rs b/src/net/server/connection_manager.rs
new file mode 100644
index 0000000..c47aa32
--- /dev/null
+++ b/src/net/server/connection_manager.rs
@@ -0,0 +1,109 @@
+//! The main server module, managing connections from clients.
+
+use super::super::packet::Packet;
+use super::connection::Connection;
+use crate::stable_vec::StableVec;
+use serde::de::DeserializeOwned;
+use serde::Serialize;
+use std::fmt::Debug;
+use std::io;
+use std::net::{SocketAddr, TcpListener};
+use std::sync::mpsc::{self, Receiver, Sender};
+use std::sync::{Arc, RwLock};
+use std::thread;
+
+/// Type of id for the connections inside of the connection manager.
+pub type ConnId = usize;
+
+/// Manages incoming connections to the servers and packets received from them.
+pub struct ConnectionManager<P: 'static + Send + Debug + DeserializeOwned> {
+ connections: Arc<RwLock<StableVec<Connection>>>,
+ local_port: u16,
+ _tx: Sender<(ConnId, Packet<P>)>,
+ rx: Receiver<(ConnId, Packet<P>)>,
+}
+
+impl<'de, P: 'static + Send + Debug + DeserializeOwned + Serialize> ConnectionManager<P> {
+ fn listen(
+ listener: TcpListener,
+ connections: Arc<RwLock<StableVec<Connection>>>,
+ packet_tx: Sender<(ConnId, Packet<P>)>,
+ ) {
+ for stream in listener.incoming() {
+ info!("Incoming connection.");
+ let stream = match stream {
+ Ok(stream) => stream,
+ Err(err) => {
+ error!("Unable to accept client. {}", err);
+ continue;
+ }
+ };
+
+ let mut connections = connections.write().unwrap();
+ let id = connections.next_free();
+ connections
+ .try_insert(id, Connection::start_rcv(id, stream, packet_tx.clone()))
+ .expect("Unable to insert client at supposedly valid id");
+ info!("Client `{}` connected.", id);
+ }
+
+ error!("Closing listener. This should never happen");
+ }
+
+ /// Start listening for connections. Returns the manager for connections,
+ /// which then can be asked about the connectins status and to send packets to
+ /// any client connected.
+ pub fn start(addr: SocketAddr) -> Result<Self, io::Error> {
+ let listener = TcpListener::bind(addr)?;
+ let local_port = listener.local_addr()?.port();
+ let connections = Arc::new(RwLock::new(StableVec::new()));
+
+ let (tx, rx) = mpsc::channel();
+ let tx_cl = tx.clone();
+ let connections_cl = connections.clone();
+ thread::spawn(move || Self::listen(listener, connections_cl, tx_cl));
+
+ Ok(Self {
+ connections,
+ local_port,
+ _tx: tx,
+ rx,
+ })
+ }
+
+ /// Try to receive the next packet. If no packet was received, this returns `None`. If the client
+ /// was disconnected, this also returns `None`.
+ pub fn next_packet(&self) -> Option<(ConnId, P)> {
+ match self.rx.try_recv() {
+ Ok((conn_id, Packet::Disconnect)) => {
+ self.connections.write().unwrap().remove(conn_id);
+ self.next_packet()
+ }
+ Ok((conn_id, Packet::Cargo(packet))) => Some((conn_id, packet)),
+ Err(_err) => None,
+ }
+ }
+
+ /// Send a packet to all clients currently connected
+ pub fn broadcast(&self, packet: P) -> bool {
+ let mut one_failed = false;
+ let mut conns = self.connections.write().unwrap();
+ let packet = Packet::Cargo(packet);
+ for (ref id, ref mut conn) in conns.id_iter_mut() {
+ if let Err(err) = conn.send(&packet) {
+ error!(
+ "Broadcasting {:?} failed for client `{}`: {:?}",
+ packet, id, err
+ );
+ one_failed = true;
+ }
+ }
+
+ !one_failed
+ }
+
+ /// Get the local port this connection manager's listener is bound to.
+ pub fn port(&self) -> u16 {
+ self.local_port
+ }
+}
diff --git a/src/net/server/mod.rs b/src/net/server/mod.rs
new file mode 100644
index 0000000..1f52ad5
--- /dev/null
+++ b/src/net/server/mod.rs
@@ -0,0 +1,6 @@
+//! Module containing network library server parts.
+
+pub mod connection;
+pub mod connection_manager;
+
+pub use connection_manager::*;
diff --git a/src/server/mod.rs b/src/server/mod.rs
new file mode 100644
index 0000000..9b55502
--- /dev/null
+++ b/src/server/mod.rs
@@ -0,0 +1,48 @@
+//! Contains the necessary ingredients to start a graf karto world server.
+
+use crate::net::server::ConnectionManager;
+use crate::net::Cargo;
+use std::io;
+use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
+use std::thread::{self, JoinHandle};
+
+/// The default port the dedicated graf karto server runs on.
+pub const DEFAULT_PORT: u16 = 44309;
+
+fn localhost(port: u16, ipv4: bool) -> SocketAddr {
+ if ipv4 {
+ SocketAddr::new(Ipv4Addr::LOCALHOST.into(), port)
+ } else {
+ SocketAddr::new(Ipv6Addr::LOCALHOST.into(), port)
+ }
+}
+
+/// Starts a thread for the server and tries to bind to the specified port. If the
+/// port cannot be bound to it fails returning an error, otherwise the join handle
+/// for the server thread is returned.
+pub fn start_with_port(port: u16, ipv4: bool) -> Result<JoinHandle<()>, io::Error> {
+ let addr = localhost(port, ipv4);
+ let conn_man = ConnectionManager::start(addr)?;
+
+ Ok(start(conn_man))
+}
+
+/// Starts a thread on any free system port. Returns an error in case that's not
+/// possible.
+pub fn start_any_port(ipv4: bool) -> Result<(JoinHandle<()>, u16), io::Error> {
+ let addr = localhost(0, ipv4);
+
+ let conn_man = ConnectionManager::start(addr)?;
+ let port = conn_man.port();
+
+ Ok((start(conn_man), port))
+}
+
+fn start(conn_man: ConnectionManager<Cargo>) -> JoinHandle<()> {
+ info!("Server started on port {}", conn_man.port());
+ thread::spawn(move || loop {
+ if let Some(cargo) = conn_man.next_packet() {
+ println!("Received cargo: {:?}", cargo);
+ }
+ })
+}
diff --git a/src/server_main.rs b/src/server_main.rs
index e69de29..d9832fd 100644
--- a/src/server_main.rs
+++ b/src/server_main.rs
@@ -0,0 +1,65 @@
+//! Dedicated server for graf_karto. Starts only the backend that the client relies on and allows
+//! connections from the outside if possible.
+
+#![allow(dead_code)]
+#![warn(missing_docs)]
+
+#[macro_use]
+extern crate log;
+
+pub mod math;
+pub mod net;
+pub mod server;
+pub mod stable_vec;
+pub mod transformable;
+pub mod world;
+
+use clap::{App, Arg};
+pub use server::*;
+
+fn main() {
+ println!(
+ "Graf Karto dedicated server version: {}",
+ clap::crate_version!()
+ );
+
+ let default_port = DEFAULT_PORT.to_string();
+ let matches = App::new("Graf Karto")
+ .version(clap::crate_version!())
+ .author(clap::crate_authors!())
+ .about(clap::crate_description!())
+ .arg(
+ Arg::with_name("port")
+ .short("p")
+ .value_name("SERVER_PORT")
+ .help("Set the port the should listen on.")
+ .default_value(&default_port),
+ )
+ .arg(
+ Arg::with_name("ipv4")
+ .short("v4")
+ .help("Use virgin IPv4 instead of chad IPv6.. you monster"),
+ )
+ .get_matches();
+
+ let use_ipv4 = matches.is_present("ipv4");
+ let port = match matches
+ .value_of("port")
+ .expect("No port found, eventhough it should have a default value")
+ .parse::<u16>()
+ {
+ Ok(port) => port,
+ Err(e) => {
+ error!("Not a valid server port: {:?}", e);
+ warn!("Using default port {}", DEFAULT_PORT);
+ DEFAULT_PORT
+ }
+ };
+
+ let server_handle = server::start_with_port(port, use_ipv4)
+ .expect("Unable to start server. Make sure the port you want to bind it on is free.");
+
+ server_handle
+ .join()
+ .expect("Server did not shut down correctly.");
+}
diff --git a/src/stable_vec.rs b/src/stable_vec.rs
index 38eb162..72a1995 100644
--- a/src/stable_vec.rs
+++ b/src/stable_vec.rs
@@ -1,11 +1,13 @@
//! Stable vec is a vector that guarantees its content access position does not change.
+use serde::{Deserialize, Serialize};
use std::ops::Deref;
use std::slice::IterMut;
/// Works like Vec, but with an additional field, where the position information is saved. Does not
/// support inserting elements in arbitrary positions, since that may shift the data. Removing data
/// in the middle is fine, however.
+#[derive(Debug, Deserialize, Serialize)]
pub struct StableVec<T> {
data: Vec<(usize, T)>,
}
@@ -22,6 +24,12 @@ impl<T> StableVec<T> {
Self { data: Vec::new() }
}
+ /// Create a stable vec from a sorted (by id, T.0) vector. Don't use if you're
+ /// not absolutely sure the data is valid.
+ pub fn from_raw_unchecked(data: Vec<(usize, T)>) -> Self {
+ Self { data }
+ }
+
/// Add an item to the end of the vector. Returns its stable id. (`len()-1 != last_element_id`)
pub fn push(&mut self, item: T) -> usize {
if self.data.is_empty() {
@@ -34,6 +42,72 @@ impl<T> StableVec<T> {
}
}
+ /// Remove all data from this vec, leaving it like a StableVec created with `new` datawise.
+ pub fn clear(&mut self) {
+ self.data.clear()
+ }
+
+ /// Find the next free space in the vec. If there is space at the end, this will be preferred,
+ /// otherwise low ids are. After checking this, `try_insert` will not fail and the returned
+ /// id can be used to insert an item at that position.
+ pub fn next_free(&self) -> usize {
+ // Check if the item can be pushed at the end.
+ if self.data.is_empty() {
+ 0
+ } else if self.data.last().unwrap().0 < usize::MAX {
+ self.data.last().unwrap().0
+ } else {
+ // Try to find a position in the vector that is still free, starting at the bottom.
+ let mut prev_id = self.data.first().unwrap().0;
+ for (_, (id, _)) in self.data.iter().enumerate().skip(1) {
+ if *id > prev_id + 1 {
+ return *id - 1;
+ }
+
+ prev_id = *id;
+ }
+
+ panic!("There is no free space in the vector. This should never happen.");
+ }
+ }
+
+ /// Attempts to insert the item at the given position. If there is no free space, an Err value
+ /// will be returned.
+ pub fn try_insert(&mut self, id: usize, item: T) -> Result<(), ()> {
+ match self.find_pos(id) {
+ // The item already exists, this is an error.
+ Ok(_) => Err(()),
+ Err(pos) => {
+ self.data.insert(pos, (id, item));
+ Ok(())
+ }
+ }
+ }
+
+ /// Tries to push the item into the vector, but if it doesn't work, it searches for an opening in
+ /// the vector where no item currently is and places it there, favouring low ids (low ids contain
+ /// potentially older changes).
+ pub fn insert_anywhere(&mut self, item: T) -> usize {
+ // Check if the item can be pushed at the end and do so if possible.
+ if self.data.is_empty() || self.data.last().unwrap().0 < usize::MAX {
+ self.push(item)
+ } else {
+ // Try to find a position in the vector that is still free, starting at the bottom.
+ let mut prev_id = self.data.first().unwrap().0;
+ for (place, (id, _)) in self.data.iter().enumerate().skip(1) {
+ if *id > prev_id + 1 {
+ let id = *id - 1;
+ self.data.insert(place, (id, item));
+ return id;
+ }
+
+ prev_id = *id;
+ }
+
+ panic!("Unable to insert element. No space left in the vector.");
+ }
+ }
+
// Find the internal position of the given id in `O(log n)`
fn find_pos(&self, id: usize) -> Result<usize, usize> {
self.data.binary_search_by(|x| x.0.cmp(&id))
@@ -84,6 +158,12 @@ impl<T> Deref for StableVec<T> {
}
}
+impl<T> Into<Vec<(usize, T)>> for StableVec<T> {
+ fn into(self) -> Vec<(usize, T)> {
+ self.data
+ }
+}
+
impl<'a, T> IdIterMut<'a, T> {
pub(super) fn new(id_vec: &'a mut [(usize, T)]) -> Self {
Self {
diff --git a/src/world/component.rs b/src/world/component.rs
new file mode 100644
index 0000000..e8a8df9
--- /dev/null
+++ b/src/world/component.rs
@@ -0,0 +1,23 @@
+//! The world is made up of components. Every component must implement this
+//! general trait that makes it possible to argue about certain characteristics
+//! and may make it possible to transform some items.
+
+use crate::math::Rect;
+use crate::transformable::NonRigidTransformable;
+
+/// Anything that can be added to the map or world must implement this trait.
+pub trait Component {
+ /// Get the rectangle that contains the component in its entirety without excess.
+ fn bounding_rect(&self) -> Rect<f64>;
+
+ /// If this component can be transformed in a non-rigid way, a dynamic
+ /// reference is returned, otherwise none.
+ fn as_non_rigid(&self) -> Option<&dyn NonRigidTransformable> {
+ None
+ }
+
+ /// The same as `as_non_rigid`, but mutably.
+ fn as_non_rigid_mut(&mut self) -> Option<&mut dyn NonRigidTransformable> {
+ None
+ }
+}
diff --git a/src/world/icon.rs b/src/world/icon.rs
new file mode 100644
index 0000000..c8945fb
--- /dev/null
+++ b/src/world/icon.rs
@@ -0,0 +1,23 @@
+//! Icons are world elements that have a specific size and cannot be stretched. They are usually used
+//! as markers for specific places in the world.
+
+use super::Component;
+use crate::math::{Rect, Vec2};
+use serde::{Deserialize, Serialize};
+
+/// The icon datatype.
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct Icon {
+ /// The id of the icon is the icons position in the currently loaded icon_data vector.
+ pub id: usize,
+ /// The position of the icon on the map, given by the vector in meters.
+ pub position: Vec2<f64>,
+ /// Rotation of the icon texture in degrees.
+ pub rotation: f64,
+}
+
+impl Component for Icon {
+ fn bounding_rect(&self) -> Rect<f64> {
+ Rect::new(self.position.x, self.position.y, 0., 0.)
+ }
+}
diff --git a/src/world/mod.rs b/src/world/mod.rs
new file mode 100644
index 0000000..047ca5c
--- /dev/null
+++ b/src/world/mod.rs
@@ -0,0 +1,113 @@
+//! The world contains all items of a session and the ways to manipulate them.
+//! Generally speaking, manipulation will be done on the server side and then
+//! the changes are sent to the client.
+
+pub mod component;
+pub mod icon;
+pub mod room;
+pub mod wall;
+
+pub use component::*;
+pub use icon::*;
+pub use room::*;
+pub use wall::*;
+
+use crate::stable_vec::StableVec;
+use ron::de::from_reader;
+use ron::ser::{to_string_pretty, PrettyConfig};
+use serde::{Deserialize, Serialize};
+use std::fs::File;
+use std::io::{self, Write};
+use std::path::Path;
+
+/// Main structure that contains all information about a world necessary to argue
+/// about structures in it. It can be persistified and loaded from disk, as well
+/// as sent over the network. Everything in it is assigned an id to make it easy
+/// to argue about a specific item. IDs may only be assigned by the server, not
+/// the client, since otherwise multiple clients may try to create the same item.
+#[derive(Debug, Serialize, Deserialize)]
+pub struct World {
+ rooms: StableVec<Room>,
+ walls: StableVec<Wall>,
+ icons: StableVec<Icon>,
+ used_ids: StableVec<()>,
+}
+
+impl World {
+ /// Load the world data from a file. Fails if the file does not exist or
+ /// cannot be correctly parsed.
+ pub fn load_from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
+ let file = File::open(&path)?;
+ let data: Self = match from_reader(file) {
+ Ok(data) => data,
+ Err(err) => {
+ return Err(io::Error::new(io::ErrorKind::InvalidData, err));
+ }
+ };
+
+ Ok(data)
+ }
+
+ /// Write the world data to the file located at `path`. If the file already
+ /// exists, it will be overwritten. If the write fails, an IO-Error is returned.
+ pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
+ let mut file = File::create(&path)?;
+
+ let pretty_conf = PrettyConfig::new()
+ .with_depth_limit(4)
+ .with_decimal_floats(true)
+ .with_separate_tuple_members(true)
+ .with_indentor("\t".to_owned());
+ let string = match to_string_pretty(&self, pretty_conf) {
+ Ok(string) => string,
+ Err(err) => {
+ return Err(io::Error::new(io::ErrorKind::InvalidInput, err));
+ }
+ };
+
+ file.write_all(&string.as_bytes())
+ }
+
+ /// Add a room to the world. Currently, holes are not supported in the polygon, but this might
+ /// change later.
+ pub fn push_room(&mut self, room: Room) -> usize {
+ let id = self.used_ids.insert_anywhere(());
+ self.rooms
+ .try_insert(id, room)
+ .expect("Id vecs out of sync");
+ id
+ }
+
+ /// Add a wall to the world.
+ pub fn push_wall(&mut self, wall: Wall) -> usize {
+ let id = self.used_ids.insert_anywhere(());
+ self.walls
+ .try_insert(id, wall)
+ .expect("Id vecs out of sync");
+ id
+ }
+
+ /// Add an icon to the world.
+ pub fn push_icon(&mut self, icon: Icon) -> usize {
+ let id = self.used_ids.insert_anywhere(());
+ self.icons
+ .try_insert(id, icon)
+ .expect("Id vecs out of sync");
+ id
+ }
+
+ /// Get all icons of the world.
+ pub fn icons(&self) -> &StableVec<Icon> {
+ &self.icons
+ }
+
+ /// Get all rooms of the world.
+ pub fn rooms(&self) -> &StableVec<Room> {
+ &self.rooms
+ }
+
+ /// Get all walls of the world.
+ pub fn walls(&self) -> &StableVec<Wall> {
+ &self.walls
+ }
+}
diff --git a/src/world/room.rs b/src/world/room.rs
new file mode 100644
index 0000000..fed8890
--- /dev/null
+++ b/src/world/room.rs
@@ -0,0 +1,52 @@
+//! Polygon rooms are the standard rooms in graf karto. They can take the form of anything that a
+//! [Polygon](crate::math::Polygon) can have.
+
+use super::Component;
+use crate::math::{Polygon, Rect};
+use crate::transformable::NonRigidTransformable;
+use nalgebra::{Matrix3, Point2};
+use serde::{Deserialize, Serialize};
+
+/// A polygon shaped room, which can be placed and modified in the world.
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub struct Room {
+ shape: Polygon<f64>,
+}
+
+impl Room {
+ /// Create a new room with the given shape.
+ pub fn new(shape: Polygon<f64>) -> Self {
+ Self { shape }
+ }
+
+ /// Get the polygon this room is based on.
+ pub fn shape(&self) -> &Polygon<f64> {
+ &self.shape
+ }
+}
+
+impl Component for Room {
+ fn bounding_rect(&self) -> Rect<f64> {
+ Rect::bounding_rect_n(&self.shape.corners())
+ }
+
+ fn as_non_rigid(&self) -> Option<&dyn NonRigidTransformable> {
+ Some(self as &dyn NonRigidTransformable)
+ }
+
+ fn as_non_rigid_mut(&mut self) -> Option<&mut dyn NonRigidTransformable> {
+ Some(self as &mut dyn NonRigidTransformable)
+ }
+}
+
+impl NonRigidTransformable for Room {
+ // XXX: This produces undefined behaviour when using a mirroring matrix, since
+ // the polygon corners must be sorted in counterclockwise direction.
+ fn apply_matrix(&mut self, matrix: &Matrix3<f64>) {
+ for corner in self.shape.corners_mut() {
+ *corner = matrix
+ .transform_point(&Point2::new(corner.x, corner.y))
+ .into();
+ }
+ }
+}
diff --git a/src/world/wall.rs b/src/world/wall.rs
new file mode 100644
index 0000000..45b0b1e
--- /dev/null
+++ b/src/world/wall.rs
@@ -0,0 +1,52 @@
+//! Walls, solid barriers that are generally unclimbable.
+//!
+//! This interpretation is generally up to the GM to decide, but generally
+//! speaking, a wall cannot be crossed by a player. If special conditions apply
+//! (for instance, when the player wants to scale the wall), a check is necessary.
+//! If a check is not necessary, then maybe you were not thinking about a wall.
+
+use super::Component;
+use crate::math::{LineSegment, Rect};
+use crate::transformable::NonRigidTransformable;
+use nalgebra::Matrix3;
+use serde::{Deserialize, Serialize};
+
+/// Representation of a solid wall a player cannot go through, except if special
+/// conditions apply.
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub struct Wall {
+ shape: LineSegment<f64>,
+}
+
+impl Wall {
+ /// Create a new wall with the line segment defining it's start and end.
+ pub fn new(shape: LineSegment<f64>) -> Self {
+ Self { shape }
+ }
+
+ /// Get the line shape this wall is based on.
+ pub fn shape(&self) -> &LineSegment<f64> {
+ &self.shape
+ }
+}
+
+impl Component for Wall {
+ fn bounding_rect(&self) -> Rect<f64> {
+ Rect::bounding_rect(self.shape.start, self.shape.end)
+ }
+
+ fn as_non_rigid(&self) -> Option<&dyn NonRigidTransformable> {
+ Some(self as &dyn NonRigidTransformable)
+ }
+
+ fn as_non_rigid_mut(&mut self) -> Option<&mut dyn NonRigidTransformable> {
+ Some(self as &mut dyn NonRigidTransformable)
+ }
+}
+
+impl NonRigidTransformable for Wall {
+ fn apply_matrix(&mut self, matrix: &Matrix3<f64>) {
+ self.shape.start = matrix.transform_point(&self.shape.start.into()).into();
+ self.shape.end = matrix.transform_point(&self.shape.end.into()).into();
+ }
+}