From 4ad143d0d8a65f0167d53c5c51b3e6553000684e Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 2 Mar 2021 11:53:01 -0700 Subject: [PATCH 1/4] Add basic architectural documentation --- docs/architecture.md | 86 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 docs/architecture.md diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 000000000..b9cb41d13 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,86 @@ +### Architecture + +Feather uses the Entity-Component-System architecture, also known as ECS. This architecture +is widely used in the Rust gamedev ecosystem. For more information, we recommend checking +out: +* the [`hecs`](https://docs.rs/hecs) documentation +* the [Bevy documentation](https://bevyengine.org/learn/book/getting-started/ecs/) + +The Feather game state is defined in the `Game` struct, which lives in `crates/common/src/game.rs`. +This struct contains the `World` (blocks) and the `Ecs` (entities). It also provides +methods for common tasks, like "spawn entity" or "remove entity" or "get block." + +### Crate Structure + +Feather is a complex codebase with multiple components. To improve modularity and reusability, we've +split the codebase into a series of crates: + +* Core Minecraft functionality (not specific to Feather) goes in [`libcraft`](https://github.com/feather-rs/libcraft). +For example, the block, item, chunk, and region file structs live in `libcraft`. `libcraft` code is intended +for use in other Rust Minecraft tools, like map editors or world file converters. +* The plugin API lives in [`quill`](https://github.com/feather-rs/quill), which actually consists of three major crates: + * `quill-common` is shared between Feather itself and plugins. This is where most of our ECS components are defined, + so that both plugins and Feather can access them. + * `quill-sys` provides FFI functions for "host calls." Host calls are low-level functions that + plugins call to perform actions. For example, "get component" and "send message" are host calls. + * `quill` is the public-facing plugin API. It reexports types from `quill-common` and wraps the FFI functions in `quill-sys` + with a safe, idiomatic API. +* The remainder of the code is in Feather itself, which consists of three major crates: + * `feather-common` implements gameplay: it defines ECS systems that run the Minecraft game. For example, it includes + physics, block placement/digging, chat, etc. It operates on the types defined in `libcraft` and `quill-common`. + * `feather-server` is a wrapper around `feather-common` that provides packet handling and sending. + * `feather-plugin-host` implements the FFI functions in `quill-sys`. + +### Adding a component + +Components should be defined in the `quill-common` crate so plugins can access them. +(Some components can also be exported from `libcraft` if they're reusable outside of Feather.) +Just define a struct for the component, derive `Serialize` and `Deserialize`, and add +it to `components.rs`. + +The component can then be accessed both from Feather and from plugins. + +### Events + +In Feather, events are components. An entity with the `PlayerJoinEvent` component just joined +the game, for example. + +The event sytsem serves as a mechanism to communicate between different crates and modules. +For example, triggering a `BlockChangeEvent` _anywhere_ causes `feather-server` to send block +update packets to players. + +To trigger an event, use `game.ecs.insert_entity_event(entity, event)`. `entity` should be the +entity that the event happened to, e.g. for `PlayerJoinEvent`, it's the player that joined. + +Alternatively, if the event is not related to a specific entity, call `game.ecs.insert_event(event)`. +For example, `BlockChangeEvent` is one such event. + +To handle events, query for entities with that component. For example, to query +for players that just joined, use: + +```rust +for (player_entity, event) in game.ecs.query::<(&PlayerJoinEvent)>().iter() { + // handle event... +} +``` + +### Sending packets + +Most features need to send packets to clients. The Minecraft protocol and its +packets are documented at [wiki.vg](https://wiki.vg/Protocol). + +In Feather, the `Client` struct in `crates/server/src/client.rs` encapsulates +the packet sending code. Add a method there to send the packet you need. + +It's not possible to send packets from the `feather-common` crate. Instead, you should +trigger an event and handle it in `feather-server`. + +### Receiving packets + +Packets are hanlded in `crates/server/src/packet_handlers.rs`. Add the necessary match +arm and implement your packet handler. + +### Further questions + +This documentation is a work in progress. Contact us on Discord if you have further +questions! From 52754d28598a38aaa3ca9530cc22cbf4665ab1ee Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 2 Mar 2021 12:41:21 -0700 Subject: [PATCH 2/4] Clarify explanations in architecture.md --- README.md | 6 +++++- docs/architecture.md | 33 ++++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 35522f2ce..5d60a1555 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ In the long term, Feather could be used on larger, more survival-like servers, w The Feather ecosystem consists of several repositories: * [`libcraft`](https://github.com/feather-rs/libcraft), a set of Rust crates providing Minecraft functionality. * [`quill`](https://github.com/feather-rs/quill), our work-in-progress plugin API. Quill plugins are written in Rust and compiled to WebAssembly. Feather runs them in a sandboxed WebAssembly VM. -* [`feather`], the server software built on top of `libcraft` and `quill`. +* `feather`, the server software built on top of `libcraft` and `quill`. ### Performance @@ -76,6 +76,10 @@ to compile Feather, but they are not guaranteed to keep working. The server executable will be located in `target/release`. +### Architecture + +For contributors, we have a work-in-progress explanation of Feather's architecture [here](docs/architecture.md). + ### FAQ * Is Feather production ready? diff --git a/docs/architecture.md b/docs/architecture.md index b9cb41d13..72c0df071 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,10 +1,28 @@ ### Architecture Feather uses the Entity-Component-System architecture, also known as ECS. This architecture -is widely used in the Rust gamedev ecosystem. For more information, we recommend checking -out: -* the [`hecs`](https://docs.rs/hecs) documentation -* the [Bevy documentation](https://bevyengine.org/learn/book/getting-started/ecs/) +is widely used in the Rust gamedev ecosystem. + +In the ECS architecture, there are three key types of objects: +* Entities: these are just IDs. In Feather, these are represented by the `Entity` struct. +They allow access to components. +* Components: these represent entities' data. Each entity can have zero or one component of every type. For example, `Position` +stores an entity position, and entities with the `Position` component have a position. You can access components +via `Game.ecs.get::()`, where `T` is the component you want. +* Systems: functions that run each tick. While components are data, systems are logic. They operate on components. + +ECS implementations allow for _queries_ that allow iteration over all entities with a specific set of components. +For example, to implement trivial physics: + +```rust +for (entity, (position, velocity)) in game.ecs.query::<(&mut Position, &Velocity)>().iter() { + *position += *velocity; +} +``` + +The above code snippet iterates over _all_ entities with `Position` and `Velocity` components. + +For more information on the ECS, we recommend checking out the [`hecs`](https://docs.rs/hecs) documentation. The Feather game state is defined in the `Game` struct, which lives in `crates/common/src/game.rs`. This struct contains the `World` (blocks) and the `Ecs` (entities). It also provides @@ -75,9 +93,14 @@ the packet sending code. Add a method there to send the packet you need. It's not possible to send packets from the `feather-common` crate. Instead, you should trigger an event and handle it in `feather-server`. +### Sending packets to nearby players + +Some packets should be sent to all players that can see a given entity, or all +players that can see a given block. Use `Server::broadcast_nearby_with` for this. + ### Receiving packets -Packets are hanlded in `crates/server/src/packet_handlers.rs`. Add the necessary match +Packets are handled in `crates/server/src/packet_handlers.rs`. Add the necessary match arm and implement your packet handler. ### Further questions From d7226799dc5242e8648a23a40bd254e9efc22dff Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 2 Mar 2021 12:48:01 -0700 Subject: [PATCH 3/4] Explain that ECS entities are a superset of Minecraft entities --- docs/architecture.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/architecture.md b/docs/architecture.md index 72c0df071..0d769a465 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -28,6 +28,10 @@ The Feather game state is defined in the `Game` struct, which lives in `crates/c This struct contains the `World` (blocks) and the `Ecs` (entities). It also provides methods for common tasks, like "spawn entity" or "remove entity" or "get block." +Note that entities in the ECS correspond either to Minecraft entities, like players or zombies, +or to internal entities like the "console entity." In general, you don't have to worry about +this distinction. + ### Crate Structure Feather is a complex codebase with multiple components. To improve modularity and reusability, we've From e0ce9b30d5f59b2b4c889020d2bdd25fbc3ca7c1 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 2 Mar 2021 13:21:57 -0700 Subject: [PATCH 4/4] example disconnect --- crates/server/src/client.rs | 18 ++++++++++++++---- crates/server/src/systems.rs | 10 +++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/server/src/client.rs b/crates/server/src/client.rs index 537643851..bed8707e8 100644 --- a/crates/server/src/client.rs +++ b/crates/server/src/client.rs @@ -8,7 +8,7 @@ use std::{ use ahash::AHashSet; use base::{ BlockId, BlockPosition, Chunk, ChunkPosition, EntityKind, Gamemode, ItemStack, Position, - ProfileProperty, + ProfileProperty, Text, }; use common::{ chat::{ChatKind, ChatMessage}, @@ -22,8 +22,8 @@ use protocol::{ self, server::{ AddPlayer, Animation, BlockChange, ChatPosition, ChunkData, ChunkDataKind, - DestroyEntities, EntityAnimation, EntityHeadLook, EntityTeleport, JoinGame, KeepAlive, - PlayerInfo, PlayerPositionAndLook, PluginMessage, SpawnPlayer, UnloadChunk, + DestroyEntities, Disconnect, EntityAnimation, EntityHeadLook, EntityTeleport, JoinGame, + KeepAlive, PlayerInfo, PlayerPositionAndLook, PluginMessage, SpawnPlayer, UnloadChunk, UpdateViewPosition, WindowItems, }, }, @@ -95,6 +95,8 @@ pub struct Client { /// The previous own position sent by the client. /// Used to detect when we need to teleport the client. client_known_position: Cell>, + + disconnected: Cell, } impl Client { @@ -113,6 +115,7 @@ impl Client { known_chunks: RefCell::new(AHashSet::new()), chunk_send_queue: RefCell::new(VecDeque::new()), client_known_position: Cell::new(None), + disconnected: Cell::new(false), } } @@ -145,7 +148,7 @@ impl Client { } pub fn is_disconnected(&self) -> bool { - self.received_packets.is_disconnected() + self.received_packets.is_disconnected() || self.disconnected.get() } pub fn known_chunks(&self) -> usize { @@ -454,6 +457,13 @@ impl Client { fn send_packet(&self, packet: impl Into) { let _ = self.packets_to_send.try_send(packet.into()); } + + pub fn disconnect(&self, reason: &str) { + self.disconnected.set(true); + self.send_packet(Disconnect { + reason: Text::from(reason.to_owned()).to_string(), + }); + } } fn chat_packet(message: ChatMessage) -> packets::server::ChatMessage { diff --git a/crates/server/src/systems.rs b/crates/server/src/systems.rs index 97eb9bd26..aa7308475 100644 --- a/crates/server/src/systems.rs +++ b/crates/server/src/systems.rs @@ -8,7 +8,7 @@ mod player_leave; mod tablist; pub mod view; -use std::time::{Duration, Instant}; +use std::{sync::atomic::AtomicUsize, time::{Duration, Instant}}; use common::Game; use ecs::{SysResult, SystemExecutor}; @@ -76,5 +76,13 @@ fn tick_clients(_game: &mut Game, server: &mut Server) -> SysResult { for client in server.clients.iter() { client.tick(); } + + static COUNTER: std::sync::atomic::AtomicUsize =AtomicUsize::new(0); + if COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst) >= 200 { + for client in server.clients.iter() { + client.disconnect("test"); + } + } + Ok(()) }