Write documentation and convert to virtual workspace
This commit is contained in:
parent
e5cb669c73
commit
5dc9d3bb8a
14
.github/workflows/clippy.yml
vendored
14
.github/workflows/clippy.yml
vendored
@ -1,12 +1,12 @@
|
||||
on: [push]
|
||||
name: clippy
|
||||
name: Clippy
|
||||
# Fail on all warnings, including clippy lints.
|
||||
env:
|
||||
RUSTFLAGS: "-Dwarnings"
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- run: rustup component add clippy
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features -- -A clippy::pedantic
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run Clippy
|
||||
run: cargo clippy --all-targets --all-features
|
||||
|
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -159,7 +159,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "composition"
|
||||
name = "composition-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
@ -169,6 +169,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
@ -341,9 +342,9 @@ checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.6"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c"
|
||||
checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
@ -472,9 +473,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.18"
|
||||
version = "0.37.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bbfc1d1c7c40c01715f47d71444744a81669ca84e8b63e25a55e169b1f86433"
|
||||
checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
|
43
Cargo.toml
43
Cargo.toml
@ -1,48 +1,29 @@
|
||||
[workspace]
|
||||
members = ["crates/*"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
authors = ["Garen Tyler <garentyler@garen.dev>"]
|
||||
repository = "https://github.com/garentyler/composition"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
edition = "2021"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.71"
|
||||
apecs = "0.7.0"
|
||||
async-trait = "0.1.68"
|
||||
byteorder = "1.4.3"
|
||||
composition-parsing = { path = "./crates/composition-parsing" }
|
||||
composition-protocol = { path = "./crates/composition-protocol" }
|
||||
composition-world = { path = "./crates/composition-world" }
|
||||
composition-core.path = "./crates/composition-core"
|
||||
composition-parsing.path = "./crates/composition-parsing"
|
||||
composition-protocol.path = "./crates/composition-protocol"
|
||||
composition-world.path = "./crates/composition-world"
|
||||
serde = { version = "1.0.160", features = ["serde_derive"] }
|
||||
serde_json = "1.0.96"
|
||||
thiserror = "1.0.40"
|
||||
tokio = { version = "1.28.0", features = ["full"] }
|
||||
tracing = { version = "0.1.37", features = ["log"] }
|
||||
|
||||
[package]
|
||||
name = "composition"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Garen Tyler <garentyler@garen.dev>"]
|
||||
description = "An extremely fast Minecraft server"
|
||||
license = "MIT"
|
||||
build = "build.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
update_1_20 = ["composition-protocol/update_1_20"]
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.21.0"
|
||||
clap = { version = "4.2.7", features = ["derive"] }
|
||||
composition-parsing = { workspace = true }
|
||||
composition-protocol = { workspace = true }
|
||||
once_cell = "1.17.1"
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-util = "0.7.8"
|
||||
toml = "0.7.3"
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["tracing-log"] }
|
||||
tracing-appender = "0.2.2"
|
||||
|
||||
# Unused but possibly useful dependencies:
|
||||
# async-trait = "0.1.48"
|
||||
# backtrace = "0.3.50"
|
||||
|
33
crates/composition-core/Cargo.toml
Normal file
33
crates/composition-core/Cargo.toml
Normal file
@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "composition-core"
|
||||
description = "An extremely fast Minecraft server"
|
||||
build = "build.rs"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "composition"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
update_1_20 = ["composition-protocol/update_1_20"]
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.21.0"
|
||||
clap = { version = "4.2.7", features = ["derive"] }
|
||||
composition-parsing.workspace = true
|
||||
composition-protocol.workspace = true
|
||||
once_cell = "1.17.1"
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-util = "0.7.8"
|
||||
toml = "0.7.3"
|
||||
tracing.workspace = true
|
||||
tracing-subscriber = { version = "0.3.17", features = ["tracing-log"] }
|
||||
tracing-appender = "0.2.2"
|
@ -14,6 +14,8 @@ pub static CONFIG: OnceCell<Config> = OnceCell::new();
|
||||
pub static ARGS: OnceCell<Args> = OnceCell::new();
|
||||
static DEFAULT_ARGS: Lazy<Args> = Lazy::new(Args::default);
|
||||
|
||||
/// Helper function to read a file from a `Path`
|
||||
/// and return its bytes as a `Vec<u8>`.
|
||||
#[tracing::instrument]
|
||||
fn read_file(path: &Path) -> std::io::Result<Vec<u8>> {
|
||||
trace!("{:?}", path);
|
||||
@ -23,6 +25,7 @@ fn read_file(path: &Path) -> std::io::Result<Vec<u8>> {
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// The main server configuration struct.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[serde(default)]
|
||||
@ -143,10 +146,13 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// All of the valid command line arguments for the composition binary.
|
||||
///
|
||||
/// Arguments will always override the config options specified in `composition.toml` or `Config::default()`.
|
||||
#[derive(Debug)]
|
||||
pub struct Args {
|
||||
pub config_file: PathBuf,
|
||||
pub server_icon: PathBuf,
|
||||
config_file: PathBuf,
|
||||
server_icon: PathBuf,
|
||||
pub log_level: Option<tracing::Level>,
|
||||
pub log_dir: PathBuf,
|
||||
}
|
10
crates/composition-core/src/error.rs
Normal file
10
crates/composition-core/src/error.rs
Normal file
@ -0,0 +1,10 @@
|
||||
/// This type represents all possible errors that can occur when running the server.
|
||||
#[allow(dead_code)]
|
||||
#[derive(thiserror::Error, Clone, Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
#[error("the server is not running")]
|
||||
NotRunning,
|
||||
}
|
||||
|
||||
/// Alias for a Result with the error type `composition_core::server::Error`.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
23
crates/composition-core/src/lib.rs
Normal file
23
crates/composition-core/src/lib.rs
Normal file
@ -0,0 +1,23 @@
|
||||
/// Server configuration and cli options.
|
||||
pub mod config;
|
||||
/// When managing the server encounters errors.
|
||||
pub(crate) mod error;
|
||||
/// Network operations.
|
||||
pub(crate) mod net;
|
||||
/// The core server implementation.
|
||||
pub(crate) mod server;
|
||||
|
||||
use crate::config::Config;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::time::Instant;
|
||||
|
||||
/// A globally accessible instant of the server's start time.
|
||||
///
|
||||
/// This should be set immediately on startup.
|
||||
pub static START_TIME: OnceCell<Instant> = OnceCell::new();
|
||||
|
||||
/// Start the server.
|
||||
#[tracing::instrument]
|
||||
pub async fn start_server() -> (server::Server, tokio_util::sync::CancellationToken) {
|
||||
server::Server::new(format!("0.0.0.0:{}", Config::instance().port)).await
|
||||
}
|
@ -1,22 +1,22 @@
|
||||
#![deny(clippy::all)]
|
||||
|
||||
use tracing::{info, warn};
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn main() {
|
||||
composition::START_TIME
|
||||
composition_core::START_TIME
|
||||
.set(std::time::Instant::now())
|
||||
.expect("could not set composition::START_TIME");
|
||||
.expect("could not set composition_core::START_TIME");
|
||||
|
||||
// Set up logging.
|
||||
let file_writer =
|
||||
tracing_appender::rolling::daily(&composition::config::Args::instance().log_dir, "log");
|
||||
let file_writer = tracing_appender::rolling::daily(
|
||||
&composition_core::config::Args::instance().log_dir,
|
||||
"log",
|
||||
);
|
||||
let (file_writer, _guard) = tracing_appender::non_blocking(file_writer);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::filter::LevelFilter::from_level(
|
||||
composition::config::Args::instance()
|
||||
composition_core::config::Args::instance()
|
||||
.log_level
|
||||
.unwrap_or(if cfg!(debug_assertions) {
|
||||
tracing::Level::DEBUG
|
||||
@ -38,7 +38,7 @@ pub fn main() {
|
||||
.init();
|
||||
|
||||
// Load the config.
|
||||
let config = composition::config::Config::load();
|
||||
let config = composition_core::config::Config::load();
|
||||
|
||||
match config.server_threads {
|
||||
Some(1) => {
|
||||
@ -61,10 +61,10 @@ pub fn main() {
|
||||
.unwrap()
|
||||
.block_on(async move {
|
||||
info!("Starting {} on port {}", config.server_version, config.port);
|
||||
let (mut server, running) = composition::start_server().await;
|
||||
let (mut server, running) = composition_core::start_server().await;
|
||||
info!(
|
||||
"Done! Start took {:?}",
|
||||
composition::START_TIME.get().unwrap().elapsed()
|
||||
composition_core::START_TIME.get().unwrap().elapsed()
|
||||
);
|
||||
|
||||
// The main server loop.
|
@ -1,22 +1,44 @@
|
||||
use crate::prelude::*;
|
||||
use composition_protocol::packets::serverbound::SL00LoginStart;
|
||||
use composition_protocol::{packets::GenericPacket, ClientState, ProtocolError};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::RwLock;
|
||||
use composition_parsing::parsable::Parsable;
|
||||
use composition_protocol::{
|
||||
packets::{serverbound::SL00LoginStart, GenericPacket},
|
||||
ClientState,
|
||||
};
|
||||
use std::{collections::VecDeque, sync::Arc, time::Instant};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::{net::TcpStream, sync::RwLock};
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
/// Similar to `composition_protocol::ClientState`,
|
||||
/// but contains more useful data for managing the client's state.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum NetworkClientState {
|
||||
pub(crate) enum NetworkClientState {
|
||||
/// A client has established a connection with the server.
|
||||
///
|
||||
/// See `composition_protocol::ClientState::Handshake` for more details.
|
||||
Handshake,
|
||||
/// The client sent `SH00Handshake` with `next_state = ClientState::Status`
|
||||
/// and is performing [server list ping](https://wiki.vg/Server_List_Ping).
|
||||
Status {
|
||||
/// When the server receives `SS00StatusRequest`, this is set
|
||||
/// to `true` and the server should send `CS00StatusResponse`.
|
||||
received_request: bool,
|
||||
/// When the server receives `SS01PingRequest`, this is set
|
||||
/// to `true` and the server should send `CS01PingResponse`
|
||||
/// and set the connection state to `Disconnected`.
|
||||
received_ping: bool,
|
||||
},
|
||||
/// The client sent `SH00Handshake` with `next_state = ClientState::Login`
|
||||
/// and is attempting to join the server.
|
||||
Login {
|
||||
received_start: (bool, Option<SL00LoginStart>),
|
||||
},
|
||||
/// The server sent `CL02LoginSuccess` and transitioned to `Play`.
|
||||
#[allow(dead_code)]
|
||||
Play,
|
||||
/// The client has disconnected.
|
||||
///
|
||||
/// No packets should be sent or received,
|
||||
/// and the `NetworkClient` should be queued for removal.
|
||||
Disconnected,
|
||||
}
|
||||
impl From<NetworkClientState> for ClientState {
|
||||
@ -42,14 +64,24 @@ impl AsRef<ClientState> for NetworkClientState {
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around the raw `TcpStream` that abstracts away reading/writing packets and bytes.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NetworkClient {
|
||||
pub(crate) struct NetworkClient {
|
||||
/// The `NetworkClient`'s unique id.
|
||||
pub id: u128,
|
||||
pub state: NetworkClientState,
|
||||
stream: Arc<RwLock<TcpStream>>,
|
||||
/// Data gets appended to the back as it gets read,
|
||||
/// and popped from the front as it gets parsed into packets.
|
||||
incoming_data: VecDeque<u8>,
|
||||
/// Packets get appended to the back as they get read,
|
||||
/// and popped from the front as they get handled.
|
||||
pub incoming_packet_queue: VecDeque<GenericPacket>,
|
||||
/// Keeps track of the last time the client sent data.
|
||||
///
|
||||
/// This is useful for removing clients that have timed out.
|
||||
pub last_received_data_time: Instant,
|
||||
/// Packets get appended to the back and get popped from the front as they get sent.
|
||||
pub outgoing_packet_queue: VecDeque<GenericPacket>,
|
||||
}
|
||||
impl NetworkClient {
|
||||
@ -99,7 +131,7 @@ impl NetworkClient {
|
||||
|
||||
if self.read_data().await.is_err() {
|
||||
self.disconnect(None).await;
|
||||
return Err(ProtocolError::Disconnected);
|
||||
return Err(composition_protocol::Error::Disconnected);
|
||||
}
|
||||
|
||||
self.incoming_data.make_contiguous();
|
||||
@ -136,7 +168,7 @@ impl NetworkClient {
|
||||
#[tracing::instrument]
|
||||
pub fn read_packet<P: std::fmt::Debug + TryFrom<GenericPacket>>(
|
||||
&mut self,
|
||||
) -> Option<Result<P, GenericPacket>> {
|
||||
) -> Option<std::result::Result<P, GenericPacket>> {
|
||||
if let Some(generic_packet) = self.incoming_packet_queue.pop_back() {
|
||||
if let Ok(packet) = TryInto::<P>::try_into(generic_packet.clone()) {
|
||||
Some(Ok(packet))
|
||||
@ -158,7 +190,7 @@ impl NetworkClient {
|
||||
for packet in packets {
|
||||
self.send_packet(packet)
|
||||
.await
|
||||
.map_err(|_| ProtocolError::Disconnected)?;
|
||||
.map_err(|_| composition_protocol::Error::Disconnected)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -167,7 +199,6 @@ impl NetworkClient {
|
||||
&self,
|
||||
packet: P,
|
||||
) -> tokio::io::Result<()> {
|
||||
use composition_parsing::Parsable;
|
||||
let packet: GenericPacket = packet.into();
|
||||
|
||||
debug!("Sending packet {:?} to client {}", packet, self.id);
|
||||
@ -189,7 +220,7 @@ impl NetworkClient {
|
||||
#[tracing::instrument]
|
||||
pub async fn disconnect(&mut self, reason: Option<composition_protocol::mctypes::Chat>) {
|
||||
use composition_protocol::packets::clientbound::{CL00Disconnect, CP17Disconnect};
|
||||
let reason = reason.unwrap_or(json!({
|
||||
let reason = reason.unwrap_or(serde_json::json!({
|
||||
"text": "You have been disconnected!"
|
||||
}));
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@ -1,25 +1,19 @@
|
||||
use crate::config::Config;
|
||||
use crate::error::Result;
|
||||
use crate::net::{NetworkClient, NetworkClientState};
|
||||
use crate::prelude::*;
|
||||
use composition_protocol::ClientState;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::{TcpListener, ToSocketAddrs};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::{sync::RwLock, task::JoinHandle};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info, trace};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ServerError {
|
||||
NotRunning,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, ServerError>;
|
||||
|
||||
/// The main state and logic of the program.
|
||||
#[derive(Debug)]
|
||||
pub struct Server {
|
||||
clients: Arc<RwLock<Vec<NetworkClient>>>,
|
||||
net_tasks_handle: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
#[tracing::instrument]
|
||||
pub async fn new<A: 'static + ToSocketAddrs + Send + std::fmt::Debug>(
|
||||
@ -110,7 +104,7 @@ impl Server {
|
||||
let _ = client.read_packets().await;
|
||||
if client.send_queued_packets().await.is_err() {
|
||||
client
|
||||
.disconnect(Some(json!({ "text": "Error writing packets." })))
|
||||
.disconnect(Some(serde_json::json!({ "text": "Error writing packets." })))
|
||||
.await;
|
||||
}
|
||||
client
|
||||
@ -194,7 +188,7 @@ impl Server {
|
||||
} else {
|
||||
client
|
||||
.disconnect(Some(
|
||||
json!({ "text": "Received invalid SH00Handshake packet" }),
|
||||
serde_json::json!({ "text": "Received invalid SH00Handshake packet" }),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
@ -216,7 +210,7 @@ impl Server {
|
||||
let config = Config::instance();
|
||||
use base64::Engine;
|
||||
client.queue_packet(CS00StatusResponse {
|
||||
response: json!({
|
||||
response: serde_json::json!({
|
||||
"version": {
|
||||
"name": config.game_version,
|
||||
"protocol": config.protocol_version
|
||||
@ -286,7 +280,9 @@ impl Server {
|
||||
// Send disconnect messages to the clients.
|
||||
for client in self.clients.write().await.iter_mut() {
|
||||
client
|
||||
.disconnect(Some(json!({ "text": "The server is shutting down." })))
|
||||
.disconnect(Some(
|
||||
serde_json::json!({ "text": "The server is shutting down." }),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
[package]
|
||||
name = "composition-parsing"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Garen Tyler <garentyler@garen.dev>"]
|
||||
description = "Useful shared parsing functions"
|
||||
license = "MIT"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
byteorder = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
byteorder.workspace = true
|
||||
serde_json.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
|
@ -1,16 +1,29 @@
|
||||
/// This type represents all possible errors that can occur when serializing or deserializing Minecraft data.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
/// This error was caused by unexpected or invalid data.
|
||||
#[error("invalid syntax")]
|
||||
Syntax,
|
||||
/// This error was caused by prematurely reaching the end of the input data.
|
||||
#[error("unexpected end of file")]
|
||||
Eof,
|
||||
/// This error was caused by reading a `composition_parsing::VarInt` that was longer than 5 bytes.
|
||||
#[error("VarInt was more than 5 bytes")]
|
||||
VarIntTooLong,
|
||||
/// This error is a wrapper for `serde_json::Error`.
|
||||
#[error(transparent)]
|
||||
InvalidJson(#[from] serde_json::Error),
|
||||
/// This error is general purpose.
|
||||
/// When possible, other error variants should be used.
|
||||
#[error("custom error: {0}")]
|
||||
Message(String),
|
||||
}
|
||||
|
||||
/// Alias for a Result with the error type `composition_parsing::Error`.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Alias for a Result that helps with zero-copy parsing.
|
||||
///
|
||||
/// The error type is `composition_parsing::Error`,
|
||||
/// and the result type is a tuple of the remaining bytes and the parsed item.
|
||||
pub type ParseResult<'data, T> = Result<(&'data [u8], T)>;
|
||||
|
@ -1,12 +1,16 @@
|
||||
#![deny(clippy::all)]
|
||||
|
||||
/// When serializing or deserializing data encounters errors.
|
||||
pub mod error;
|
||||
/// The `Parsable` trait, and implementations for useful types.
|
||||
pub mod parsable;
|
||||
/// Useful re-exports.
|
||||
pub mod prelude {
|
||||
pub use crate::{parsable::Parsable, take_bytes, VarInt};
|
||||
}
|
||||
|
||||
pub use error::{Error, ParseResult, Result};
|
||||
pub use parsable::Parsable;
|
||||
pub use serde_json;
|
||||
|
||||
/// Returns a function that returns a `ParseResult<&[u8]>`, where the slice is size `num`.
|
||||
pub fn take_bytes(num: usize) -> impl Fn(&'_ [u8]) -> ParseResult<'_, &'_ [u8]> {
|
||||
move |data| {
|
||||
use std::cmp::Ordering;
|
||||
@ -19,6 +23,10 @@ pub fn take_bytes(num: usize) -> impl Fn(&'_ [u8]) -> ParseResult<'_, &'_ [u8]>
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of the protocol's VarInt type.
|
||||
///
|
||||
/// Simple wrapper around an i32, but is parsed and serialized differently.
|
||||
/// When the original i32 value is needed, simply `Deref` it.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||
pub struct VarInt(i32);
|
||||
impl std::ops::Deref for VarInt {
|
||||
@ -53,15 +61,6 @@ impl std::fmt::Display for VarInt {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ClientState {
|
||||
Handshake,
|
||||
Status,
|
||||
Login,
|
||||
Play,
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -1,12 +1,21 @@
|
||||
use crate::{take_bytes, Error, ParseResult, VarInt};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
|
||||
/// A structure that can be serialized and deserialized.
|
||||
///
|
||||
/// Similar to serde's `Serialize` and `Deserialize` traits.
|
||||
pub trait Parsable {
|
||||
/// Attempt to parse (deserialize) `Self` from the given byte slice.
|
||||
fn parse(data: &[u8]) -> ParseResult<'_, Self>
|
||||
where
|
||||
Self: Sized;
|
||||
/// Serialize `self` into a vector of bytes.
|
||||
fn serialize(&self) -> Vec<u8>;
|
||||
|
||||
/// Helper to optionally parse `Self`.
|
||||
///
|
||||
/// An `Option<T>` is represented in the protocol as
|
||||
/// a boolean optionally followed by `T` if the boolean was true.
|
||||
fn parse_optional(data: &[u8]) -> ParseResult<'_, Option<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
@ -19,6 +28,10 @@ pub trait Parsable {
|
||||
Ok((data, None))
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to parse `num` repetitions of `Self`.
|
||||
///
|
||||
/// Useful with an array of known length.
|
||||
fn parse_repeated(num: usize, mut data: &[u8]) -> ParseResult<'_, Vec<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
@ -31,6 +44,11 @@ pub trait Parsable {
|
||||
}
|
||||
Ok((data, output))
|
||||
}
|
||||
|
||||
/// Helper to parse an array of `Self`, when the length is unknown.
|
||||
///
|
||||
/// In the protocol, arrays are commonly prefixed with their length
|
||||
/// as a `VarInt`.
|
||||
fn parse_vec(data: &[u8]) -> ParseResult<'_, Vec<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
|
@ -1,19 +1,20 @@
|
||||
[package]
|
||||
name = "composition-protocol"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Garen Tyler <garentyler@garen.dev>"]
|
||||
description = "The Minecraft protocol implemented in a network-agnostic way"
|
||||
license = "MIT"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
update_1_20 = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
composition-parsing = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
anyhow.workspace = true
|
||||
byteorder.workspace = true
|
||||
composition-parsing.workspace = true
|
||||
serde.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
|
@ -11,7 +11,7 @@ use crate::{
|
||||
blocks::BlockPosition,
|
||||
mctypes::{Chat, Uuid, VarInt},
|
||||
};
|
||||
use composition_parsing::{Parsable, ParseResult};
|
||||
use composition_parsing::{parsable::Parsable, ParseResult};
|
||||
|
||||
pub type EntityId = VarInt;
|
||||
pub type EntityUuid = Uuid;
|
||||
|
26
crates/composition-protocol/src/error.rs
Normal file
26
crates/composition-protocol/src/error.rs
Normal file
@ -0,0 +1,26 @@
|
||||
/// This type represents all possible errors that can occur in the Minecraft protocol.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
/// This error was caused by unexpected or invalid data.
|
||||
#[error("invalid syntax")]
|
||||
Syntax,
|
||||
/// This error was caused by prematurely reaching the end of the input data.
|
||||
#[error("unexpected end of file")]
|
||||
Eof,
|
||||
/// The connection did not receive data and timed out.
|
||||
#[error("stream timed out")]
|
||||
Timeout,
|
||||
/// This error was caused by attempting to send or receive data from a disconnected client.
|
||||
#[error("communicating to disconnected client")]
|
||||
Disconnected,
|
||||
/// This error is a wrapper for `composition_parsing::Error`.
|
||||
#[error(transparent)]
|
||||
ParseError(#[from] composition_parsing::Error),
|
||||
/// This error is general purpose.
|
||||
/// When possible, other error variants should be used.
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
/// Alias for a Result with the error type `composition_protocol::Error`.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
@ -1,28 +1,45 @@
|
||||
#![deny(clippy::all)]
|
||||
|
||||
/// Implementation of Minecraft's blocks.
|
||||
pub mod blocks;
|
||||
/// Implementation of Minecraft's entities.
|
||||
pub mod entities;
|
||||
/// When using the protocol encounters errors.
|
||||
pub mod error;
|
||||
/// Implementation of Minecraft's items and inventories.
|
||||
pub mod inventory;
|
||||
/// Useful types for representing the Minecraft protocol.
|
||||
pub mod mctypes;
|
||||
/// Network packets.
|
||||
///
|
||||
/// The packet naming convention used is "DSIDName" where
|
||||
/// 'D' is either 'S' for serverbound or 'C' for clientbound,
|
||||
/// 'S' is the current connection state (**H**andshake, **S**tatus, **L**ogin, or **P**lay),
|
||||
/// "ID" is the packet id in uppercase hexadecimal (ex. 1B, 05, 3A),
|
||||
/// and "Name" is the packet's name as found on [wiki.vg](https://wiki.vg/Protocol) in PascalCase.
|
||||
/// Examples include "SH00Handshake", "CP00SpawnEntity", and "SP11KeepAlive".
|
||||
pub mod packets;
|
||||
|
||||
use thiserror::Error;
|
||||
pub use error::{Error, Result};
|
||||
|
||||
pub use composition_parsing::ClientState;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ProtocolError {
|
||||
#[error("invalid data")]
|
||||
InvalidData,
|
||||
#[error("not enough data")]
|
||||
NotEnoughData,
|
||||
#[error("stream timed out")]
|
||||
Timeout,
|
||||
#[error("communicating to disconnected client")]
|
||||
/// Enum representation of the connection's current state.
|
||||
///
|
||||
/// Parsing packets requires knowing which state the connection is in.
|
||||
/// [Relevant wiki.vg page](https://wiki.vg/How_to_Write_a_Server#FSM_example_of_handling_new_TCP_connections)
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ClientState {
|
||||
/// The connection is freshly established.
|
||||
///
|
||||
/// The only packet in this state is `SH00Handshake`.
|
||||
/// After this packet is sent, the connection immediately
|
||||
/// transitions to `Status` or `Login`.
|
||||
Handshake,
|
||||
/// The client is performing [server list ping](https://wiki.vg/Server_List_Ping).
|
||||
Status,
|
||||
/// The client is attempting to join the server.
|
||||
///
|
||||
/// The `Login` state includes authentication, encryption, compression, and plugins.
|
||||
Login,
|
||||
/// The main connection state. The client has authenticated and is playing on the server.
|
||||
Play,
|
||||
/// The client has disconnected, and the connection struct should be removed. No packets should be sent or received.
|
||||
Disconnected,
|
||||
#[error(transparent)]
|
||||
ParseError(#[from] composition_parsing::Error),
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, ProtocolError>;
|
||||
|
@ -1,10 +1,14 @@
|
||||
use composition_parsing::Parsable;
|
||||
use composition_parsing::parsable::Parsable;
|
||||
|
||||
/// Alias for a u128.
|
||||
pub type Uuid = u128;
|
||||
pub use composition_parsing::VarInt;
|
||||
/// Alias for a `serde_json::Value`.
|
||||
pub type Json = composition_parsing::serde_json::Value;
|
||||
/// Alias for a `Json`.
|
||||
pub type Chat = Json;
|
||||
|
||||
/// An implementation of the protocol's [Position](https://wiki.vg/Protocol#Position) type.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Position {
|
||||
pub x: i32,
|
||||
@ -41,6 +45,7 @@ impl Parsable for Position {
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum of the possible difficulties in Minecraft.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Difficulty {
|
||||
Peaceful = 0,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::mctypes::{Chat, Json, Uuid, VarInt};
|
||||
use composition_parsing::Parsable;
|
||||
use composition_parsing::parsable::Parsable;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CL00Disconnect {
|
||||
|
@ -1,5 +1,8 @@
|
||||
/// Packets for the `ClientState::Login` state.
|
||||
pub mod login;
|
||||
/// Packets for the `ClientState::Play` state.
|
||||
pub mod play;
|
||||
/// Packets for the `ClientState::Status` state.
|
||||
pub mod status;
|
||||
|
||||
pub use login::*;
|
||||
|
@ -1,16 +1,16 @@
|
||||
/// Packets that are heading to the client.
|
||||
pub mod clientbound;
|
||||
/// Packets that are heading to the server.
|
||||
pub mod serverbound;
|
||||
|
||||
use crate::mctypes::VarInt;
|
||||
use composition_parsing::prelude::*;
|
||||
|
||||
pub type PacketId = crate::mctypes::VarInt;
|
||||
/// Alias for a `VarInt`.
|
||||
pub type PacketId = VarInt;
|
||||
|
||||
pub trait Packet:
|
||||
std::fmt::Debug
|
||||
+ Clone
|
||||
+ TryFrom<GenericPacket>
|
||||
+ Into<GenericPacket>
|
||||
+ composition_parsing::Parsable
|
||||
std::fmt::Debug + Clone + TryFrom<GenericPacket> + Into<GenericPacket> + Parsable
|
||||
{
|
||||
const ID: i32;
|
||||
const CLIENT_STATE: crate::ClientState;
|
||||
@ -32,7 +32,7 @@ macro_rules! generic_packet {
|
||||
is_serverbound: bool,
|
||||
data: &'data [u8]
|
||||
) -> composition_parsing::ParseResult<'data, Self> {
|
||||
use composition_parsing::Parsable;
|
||||
use composition_parsing::parsable::Parsable;
|
||||
tracing::trace!(
|
||||
"GenericPacket::parse_uncompressed: {:?} {} {:?}",
|
||||
client_state,
|
||||
@ -60,7 +60,7 @@ macro_rules! generic_packet {
|
||||
is_serverbound: bool,
|
||||
data: &'data [u8],
|
||||
) -> composition_parsing::ParseResult<'data, Self> {
|
||||
use composition_parsing::Parsable;
|
||||
use composition_parsing::parsable::Parsable;
|
||||
tracing::trace!(
|
||||
"GenericPacket::parse_body: {:?} {} {}",
|
||||
client_state,
|
||||
@ -77,7 +77,7 @@ macro_rules! generic_packet {
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn serialize(&self) -> (crate::packets::PacketId, Vec<u8>) {
|
||||
use composition_parsing::Parsable;
|
||||
use composition_parsing::parsable::Parsable;
|
||||
tracing::trace!("GenericPacket::serialize: {:?}", self);
|
||||
match self {
|
||||
$(
|
||||
@ -145,7 +145,7 @@ macro_rules! packet {
|
||||
const CLIENT_STATE: crate::ClientState = $client_state;
|
||||
const IS_SERVERBOUND: bool = $serverbound;
|
||||
}
|
||||
impl composition_parsing::Parsable for $packet_type {
|
||||
impl composition_parsing::parsable::Parsable for $packet_type {
|
||||
#[tracing::instrument]
|
||||
fn parse<'data>(data: &'data [u8]) -> composition_parsing::ParseResult<'_, Self> {
|
||||
$parse_body(data)
|
||||
|
@ -1,6 +1,10 @@
|
||||
/// Packets for the `ClientState::Handshake` state.
|
||||
pub mod handshake;
|
||||
/// Packets for the `ClientState::Login` state.
|
||||
pub mod login;
|
||||
/// Packets for the `ClientState::Play` state.
|
||||
pub mod play;
|
||||
/// Packets for the `ClientState::Status` state.
|
||||
pub mod status;
|
||||
|
||||
pub use handshake::*;
|
||||
|
@ -1,13 +1,14 @@
|
||||
[package]
|
||||
name = "composition-world"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Garen Tyler <garentyler@garen.dev>"]
|
||||
description = "A Minecraft world generator implementation that allows for custom worlds"
|
||||
license = "MIT"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
composition-protocol = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
composition-protocol.workspace = true
|
||||
thiserror.workspace = true
|
||||
|
@ -4,6 +4,8 @@ use crate::{
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// `Chunk`s divide the world into smaller parts
|
||||
/// and manage the blocks and entities within.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Chunk {
|
||||
// blocks[x][y][z]
|
||||
@ -19,9 +21,13 @@ impl Default for Chunk {
|
||||
}
|
||||
}
|
||||
|
||||
/// Position for a `Chunk`.
|
||||
///
|
||||
/// To convert to block positions, multiply by a factor of 16.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Default, Eq, Hash)]
|
||||
pub struct ChunkPosition {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub z: i32,
|
||||
}
|
||||
impl From<BlockPosition> for ChunkPosition {
|
||||
@ -29,6 +35,7 @@ impl From<BlockPosition> for ChunkPosition {
|
||||
// Divide by 16 to get the chunk.
|
||||
ChunkPosition {
|
||||
x: value.x >> 4,
|
||||
y: value.y >> 4,
|
||||
z: value.z >> 4,
|
||||
}
|
||||
}
|
||||
@ -38,6 +45,7 @@ impl From<EntityPosition> for ChunkPosition {
|
||||
// Divide by 16 and convert to i32.
|
||||
ChunkPosition {
|
||||
x: (value.x / 16.0) as i32,
|
||||
y: (value.y / 16.0) as i32,
|
||||
z: (value.z / 16.0) as i32,
|
||||
}
|
||||
}
|
||||
|
13
crates/composition-world/src/error.rs
Normal file
13
crates/composition-world/src/error.rs
Normal file
@ -0,0 +1,13 @@
|
||||
/// This type represents all possible errors that can occur when managing a `World`.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("the given position was out of bounds")]
|
||||
OutOfBounds,
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
/// Alias for a Result with the error type `composition_world::Error`.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
@ -1 +1,2 @@
|
||||
|
||||
/// An implementation of Minecraft's superflat world type.
|
||||
pub mod superflat;
|
||||
|
4
crates/composition-world/src/generators/superflat.rs
Normal file
4
crates/composition-world/src/generators/superflat.rs
Normal file
@ -0,0 +1,4 @@
|
||||
/// An implementation of Minecraft's superflat world type.
|
||||
pub struct Superflat;
|
||||
|
||||
// TODO: Implement superflat world.
|
@ -1,56 +1,70 @@
|
||||
#![deny(clippy::all)]
|
||||
|
||||
/// Worlds are divided into chunks.
|
||||
pub mod chunks;
|
||||
/// When managing a `World` encounters errors.
|
||||
pub mod error;
|
||||
/// Default implementations of `World`, such as `Superflat`.
|
||||
pub mod generators;
|
||||
/// Useful re-exports.
|
||||
pub mod prelude {
|
||||
pub use crate::{chunks::Chunk, World};
|
||||
}
|
||||
|
||||
pub use composition_protocol::{blocks, entities};
|
||||
pub use error::{Error, Result};
|
||||
|
||||
use crate::chunks::ChunkPosition;
|
||||
use blocks::BlockPosition;
|
||||
use crate::chunks::{Chunk, ChunkPosition};
|
||||
use blocks::{Block, BlockPosition};
|
||||
use entities::{Entity, EntityId, EntityPosition};
|
||||
use std::path::Path;
|
||||
use thiserror::Error;
|
||||
|
||||
/// A `World` abstracts away world generation, updating blocks, and saving.
|
||||
#[async_trait::async_trait]
|
||||
pub trait World {
|
||||
/// Get the world's name.
|
||||
fn name() -> String;
|
||||
/// Create a new world.
|
||||
/// Create a new world from a seed.
|
||||
fn new(seed: u128) -> Self;
|
||||
/// Load an existing world.
|
||||
async fn load_from_dir<P: AsRef<Path> + Send>(world_dir: P) -> Result<Self>
|
||||
/// Load an existing world from a directory.
|
||||
async fn load<P: AsRef<Path> + Send>(world_dir: P) -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
/// Save the world to a directory.
|
||||
async fn save_to_dir<P: AsRef<Path> + Send>(&self, world_dir: P) -> Result<()>;
|
||||
async fn save<P: AsRef<Path> + Send>(&self, world_dir: P) -> Result<()>;
|
||||
|
||||
async fn is_chunk_loaded(&self, chunk_pos: ChunkPosition) -> bool;
|
||||
/// Check whether a chunk is loaded or not.
|
||||
fn is_chunk_loaded(&self, chunk_pos: ChunkPosition) -> bool;
|
||||
/// Load a chunk if it's unloaded, does nothing if the chunk is already loaded.
|
||||
async fn load_chunk(&self, chunk_pos: ChunkPosition) -> Result<()>;
|
||||
/// Unload a chunk if it's loaded, does nothing if the chunk is already unloaded.
|
||||
async fn unload_chunk(&self, chunk_pos: ChunkPosition) -> Result<()>;
|
||||
async fn get_chunk(&self, chunk_pos: ChunkPosition) -> Result<chunks::Chunk>;
|
||||
async fn set_chunk(&self, chunk_pos: ChunkPosition, chunk: chunks::Chunk) -> Result<()>;
|
||||
/// Gets a copy of the chunk at the given `ChunkPosition`.
|
||||
async fn get_chunk(&self, chunk_pos: ChunkPosition) -> Result<Chunk>;
|
||||
/// Sets the chunk at the given `ChunkPosition`.
|
||||
async fn set_chunk(&self, chunk_pos: ChunkPosition, chunk: Chunk) -> Result<()>;
|
||||
|
||||
// Getting/setting blocks requires async because the chunk might not be loaded.
|
||||
async fn get_block(&self, block_pos: BlockPosition) -> Result<blocks::Block>;
|
||||
async fn set_block(&self, block_pos: BlockPosition, block: blocks::Block) -> Result<()>;
|
||||
/// Get the block at the given `BlockPosition`.
|
||||
///
|
||||
/// Async because the containing chunk might need to be loaded.
|
||||
async fn get_block(&self, block_pos: BlockPosition) -> Result<Block>;
|
||||
/// Set the block at the given `BlockPosition`.
|
||||
///
|
||||
/// Async because the containing chunk might need to be loaded.
|
||||
async fn set_block(&self, block_pos: BlockPosition, block: Block) -> Result<()>;
|
||||
|
||||
// Spawning/removing entities requires async because the chunk might not be loaded.
|
||||
async fn spawn_entity(
|
||||
&self,
|
||||
entity_pos: entities::EntityPosition,
|
||||
entity: entities::Entity,
|
||||
) -> Result<entities::EntityId>;
|
||||
fn get_entity(&self, entity_id: entities::EntityId) -> Result<&entities::Entity>;
|
||||
fn get_entity_mut(&self, entity_id: entities::EntityId) -> Result<&mut entities::Entity>;
|
||||
async fn remove_entity(&self, entity_id: entities::EntityId) -> Result<()>;
|
||||
/// Spawn an entity at the given `EntityPosition`.
|
||||
///
|
||||
/// Async because the containing chunk might need to be loaded.
|
||||
async fn spawn_entity(&self, entity_pos: EntityPosition, entity: Entity) -> Result<EntityId>;
|
||||
/// Get a reference to the entity with the given `EntityId`.
|
||||
/// Returns Err if no entity could be found with that id.
|
||||
fn get_entity(&self, entity_id: EntityId) -> Result<&Entity>;
|
||||
/// Get a mutable reference to the entity with the given `EntityId`.
|
||||
/// Returns Err if no entity could be found with that id.
|
||||
fn get_entity_mut(&self, entity_id: EntityId) -> Result<&mut Entity>;
|
||||
/// Remove the entity with the given `EntityId`.
|
||||
///
|
||||
/// Async because the containing chunk might need to be loaded.
|
||||
///
|
||||
/// This should not kill the entity, it should simply remove it from processing.
|
||||
async fn remove_entity(&self, entity_id: EntityId) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum WorldError {
|
||||
#[error("the given position was out of bounds")]
|
||||
OutOfBounds,
|
||||
#[error(transparent)]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, WorldError>;
|
||||
|
34
src/lib.rs
34
src/lib.rs
@ -1,34 +0,0 @@
|
||||
pub mod config;
|
||||
pub mod net;
|
||||
pub mod server;
|
||||
|
||||
use crate::config::Config;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::time::Instant;
|
||||
|
||||
pub static START_TIME: OnceCell<Instant> = OnceCell::new();
|
||||
|
||||
/// Start the server.
|
||||
#[tracing::instrument]
|
||||
pub async fn start_server() -> (server::Server, tokio_util::sync::CancellationToken) {
|
||||
server::Server::new(format!("0.0.0.0:{}", Config::instance().port)).await
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::config::Config;
|
||||
pub use crate::START_TIME;
|
||||
pub use composition_protocol::mctypes::{Chat, Json, Uuid};
|
||||
pub use serde::{Deserialize, Serialize};
|
||||
pub use serde_json::json;
|
||||
pub use std::collections::VecDeque;
|
||||
pub use std::io::{Read, Write};
|
||||
pub use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
pub use tracing::{debug, error, info, trace, warn};
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ParseError {
|
||||
NotEnoughData,
|
||||
InvalidData,
|
||||
VarIntTooBig,
|
||||
}
|
||||
pub type ParseResult<T> = Result<(T, usize), ParseError>;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ServerboundMessage {
|
||||
Chat(String), // The chat message.
|
||||
PlayerJoin(String, String), // UUID, then username
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ClientboundMessage {
|
||||
Chat(String), // The chat message.
|
||||
Disconnect(String), // The reason for disconnecting.
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user