forked from feather-rs/feather
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathlib.rs
More file actions
524 lines (464 loc) · 17.3 KB
/
lib.rs
File metadata and controls
524 lines (464 loc) · 17.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
//! Feather, a Minecraft server implementation in Rust.
//!
//! This is the developer documenation, and anyone wishing to contribute
//! should read this first.
//!
//! The core of Feather is based on [`legion`](https://github.com/TomGillen/legion),
//! a fast ECS for Rust, and [`tonks`](https://github.com/feather-rs/tonks), a system
//! scheduler built on Legion. As a result, we use the ECS architecture: the
//! entire server consists of _entities_, simple IDs with no data; _components_,
//! arbitrary data, such as positions, which can be attached to an entity;
//! and _systems_, functions which can run logic over entities and components.
//!
//! The benefit of this design is the splitting between data and logic. With a traditional
//! object-oriented design, there would be an Entity class from which other entities
//! inherit and can override logic. However, this model does not work well with Rust's
//! borrow checker (as we found out in the early days of Feather, when this design was
//! used), and more importantly, it reduces flexibility. Say, for example, that a plugin
//! wants to modify the physics behavior of a cow by increasing gravity. With the object-oriented
//! design, it would have to somehow modify the `run_physics` method on `Cow`, which is not
//! possible in a native language (although it can be done in some languages using class rewriting).
//! On the other hand, using Feather, there is a `Physics` component which stores gravity,
//! drag, etc. for an entity, and all the plugin has to do is modify that component.
//!
//! Another benefit of the ECS architecture is performance. With the OO design, entities
//! would likely be stored in a `Vec<Box<dyn Entity>>`, which is horribly inefficient
//! with regards to cache locality and iteration performance. Legion, however, stores
//! entities in an efficient manner such that many of the same type of component
//! are stored contiguously, which is excellent for cache performance.
//!
//! Here is a more in-depth description of each concept in Feather.
//!
//! ## Entities
//! Entities, or `legion::entity::Entity`, are simple numerical IDs: they store no
//! data, but components can be attached to an entity. See the systems section
//! for information on how to access this data.
//!
//! ## Components
//! Components store data associated with an entity, such as `Position`.
//! Arbitrary amounts of components can be associated with any given entity.
//!
//! ## Resources
//! Resources are a branching off from the pure ECS concept. Like components, they
//! store arbitrary data in the form of structs; however, they are not associated
//! with any entity. An example of a resource might be the chunk map, which allows
//! access to blocks in the world.
//!
//! ## Systems
//! The systems concept is where things become more complex. `tonks`, the library
//! which runs systems, runs them _in parallel_, effectively multithreading the
//! entire server. This is still safe because the scheduler ensures that no
//! two systems which write to the same data run at the same time.
//!
//! A consequence of the above is that systems must explicitly state which
//! resources and components they might access. If a system needs access to positions,
//! it needs to state this upfront so the scheduler can ensure memory safety.
//!
//! Systems can be written by creating a function annotated with the `system` attribute.
//! `tonks` will automatically detect which resources are accessed based on the function
//! parameters, and it will register them to the system scheduler without you
//! having to do anything. (How does this work? Don't even bother.)
//!
//! ## Events
//! Events are another concept unique to Feather, at least in the way they are implemented
//! here. The name states what they are—BlockChangeEvent, for example, is triggered when
//! a block is updated.
//!
//! A system can trigger an event by specifying an `&mut Trigger<E>` resource.
//!
//! ## Event handlers
//! Event handlers are similar to systems, but they only run when the event they
//! handle is triggered. Use the `event_handler` attribute on a function to register
//! it as an event handler.
//!
//! # Networking model
//! Feather consists of two key parts: the server threads, which run systems
//! over entities, and the networking tasks, which run on [`tokio`](https://github.com/tokio-rs/tokio).
//! The networking tasks will accept connections and parse any packets received
//! from the player.
//!
//! When a connection is first made to the server's TCP listener, the networking
//! task will spawn another task to handle the connection. It's important to note that
//! at this time, _the server thread is totally unaware of the connection_—networking
//! runs entirely isolated from the rest of the program.
//!
//! At this point, the initial handler takes over, which runs on the networking task.
//! It will handle the login sequence or status pings, perform authentication, etc.
//! If successful, the server is notified of the new player through a channel.
//!
//! When the server is notified of a new player, it's essential to realize
//! that the player still hasn't been sent important data, such as
//! chunk packets, inventory, time, nearby entities, etc. `PlayerJoinEvent`
//! is used to send this data.
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde;
#[macro_use]
extern crate serde_json;
#[macro_use]
extern crate smallvec;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate feather_core;
#[macro_use]
extern crate fecs;
extern crate nalgebra_glm as glm;
use crossbeam::Receiver;
use std::sync::atomic::AtomicU32;
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::chunk_logic::ChunkWorkerHandle;
use crate::config::Config;
use crate::game::Game;
use crate::packet_buffer::PacketBuffers;
use crate::time::Time;
use crate::worldgen::{
ComposableGenerator, EmptyWorldGenerator, SuperflatWorldGenerator, WorldGenerator,
};
use feather_core::level::{deserialize_level_file, save_level_file, LevelData, LevelGeneratorType};
use feather_core::world::ChunkMap;
use feather_core::{level, ChunkPosition};
use fecs::{EntityBuilder, Executor, Resources, World};
use jemallocator::Jemalloc;
use rand::Rng;
use std::collections::hash_map::DefaultHasher;
use std::fs::File;
use std::hash::{Hash, Hasher};
use std::io::{Read, Write};
use std::path::Path;
use std::process::exit;
use thread_local::CachedThreadLocal;
#[global_allocator]
static ALLOC: Jemalloc = Jemalloc;
mod block;
mod broadcasters;
mod chat;
mod chunk_entities;
pub mod chunk_logic;
pub mod chunk_worker;
pub mod config;
pub mod entity;
pub mod game;
pub mod io;
mod join;
mod lighting;
mod load;
pub mod network;
pub mod p_inventory; // Prefixed to avoid conflict with inventory crate
pub mod packet_buffer;
mod packet_handlers;
pub mod physics;
pub mod player;
mod save;
pub mod shutdown;
mod systems;
mod time;
pub mod util;
mod view;
mod weather;
pub mod worldgen;
pub type BumpVec<'a, T> = bumpalo::collections::Vec<'a, T>;
pub const TPS: u64 = 20;
pub const PROTOCOL_VERSION: u32 = 404;
pub const SERVER_VERSION: &str = "Feather 1.13.2";
pub const TICK_TIME: u64 = 1000 / TPS;
pub fn main() {
let config = Arc::new(load_config());
init_log(&config);
info!("Starting Feather; please wait...");
let server_icon = Arc::new(load_server_icon());
let player_count = Arc::new(AtomicU32::new(0));
let packet_buffers = Arc::new(PacketBuffers::new());
let io_handle = init_io_manager(
Arc::clone(&config),
Arc::clone(&player_count),
Arc::clone(&server_icon),
Arc::clone(&packet_buffers),
);
let world_name = &config.world.name;
let world_dir = Path::new(world_name.as_str());
let level_file = &world_dir.join("level.dat");
if !world_dir.is_dir() {
info!(
"World directory '{}' not found, creating it",
world_dir.display()
);
// Create directory
std::fs::create_dir(world_dir).unwrap();
let level = create_level(&config);
let root = level::Root { data: level };
let mut level_file = File::create(level_file).unwrap();
save_level_file(&root, &mut level_file).unwrap();
}
info!("Loading {}", level_file.to_str().unwrap());
let level = load_level(level_file).unwrap_or_else(|e| {
error!("Error occurred while loading level.dat: {}", e);
error!("Please ensure that the world directory exists and is not corrupt.");
exit(1)
});
let chunk_worker_handle = init_chunk_worker(world_dir, &level);
let time = Time(level.time as u64);
let game = Game {
io_handle,
config,
tick_count: 0,
player_count,
level,
chunk_map: ChunkMap::new(),
bump: CachedThreadLocal::new(),
chunk_worker_handle,
chunk_unload_queue: Default::default(),
chunk_holders: Default::default(),
chunks_to_send: Default::default(),
chunk_entities: Default::default(),
rng: CachedThreadLocal::new(),
time,
save_queue: Default::default(),
lighting_worker_handle: lighting::start_worker(),
};
let (executor, resources) = init_executor(game, packet_buffers);
let mut world = World::new();
// Channel used by the shutdown handler to notify the server thread.
let (shutdown_tx, shutdown_rx) = crossbeam::bounded(1);
shutdown::init(shutdown_tx);
info!("Initialized world");
info!("Generating RSA keypair");
io::init();
info!("Queuing spawn chunks for loading");
load_spawn_chunks(&mut *resources.get_mut::<Game>(), &mut world);
info!("Server started");
run_loop(&mut world, &resources, &executor, shutdown_rx);
info!("Shutting down");
info!("Disconnecting players");
shutdown::disconnect_players(&world);
info!("Shutting down workers");
shutdown::shut_down_workers(&*resources.get::<Game>());
info!("Saving chunks");
shutdown::save_chunks(&*resources.get::<Game>(), &world);
info!("Saving level.dat");
shutdown::save_level(&mut *resources.get_mut::<Game>());
info!("Saving player data");
shutdown::save_player_data(&world);
info!("Goodbye");
exit(0);
}
/// Runs the main game loop.
fn run_loop(
world: &mut World,
resources: &Resources,
executor: &Executor,
shutdown_rx: Receiver<()>,
) {
loop {
if shutdown_rx.try_recv().is_ok() {
// Shut down
return;
}
let start_time = current_time_in_millis();
executor.execute(resources, world);
world.defrag(Some(256)); // should this be done at an interval rate?
// Sleep correct amount
let end_time = current_time_in_millis();
let elapsed = end_time - start_time;
if elapsed > TICK_TIME {
debug!("Running behind! Starting next tick immediately");
continue; // Behind - start next tick immediately
}
// Sleep in 1ms increments until we've slept enough
let mut sleep_time = (TICK_TIME - elapsed) as i64;
let mut last_sleep_time = current_time_in_millis();
while sleep_time > 0 {
std::thread::sleep(Duration::from_millis(1));
sleep_time -= (current_time_in_millis() - last_sleep_time) as i64;
last_sleep_time = current_time_in_millis();
}
}
}
/// Initializes the executor and resources.
fn init_executor(game: Game, packet_buffers: Arc<PacketBuffers>) -> (Executor, Resources) {
let mut resources = Resources::new();
resources.insert(game);
resources.insert(packet_buffers);
let executor = systems::build_executor();
(executor, resources)
}
/// Initializes the chunk worker.
fn init_chunk_worker(world_dir: &Path, level: &LevelData) -> ChunkWorkerHandle {
let generator: Arc<dyn WorldGenerator> = match level.generator_type() {
LevelGeneratorType::Flat => Arc::new(SuperflatWorldGenerator {
options: level.clone().generator_options.unwrap_or_default(),
}),
LevelGeneratorType::Default => {
Arc::new(ComposableGenerator::default_with_seed(level.seed as u64))
}
_ => Arc::new(EmptyWorldGenerator {}),
};
let (tx, rx) = chunk_worker::start(world_dir, generator);
ChunkWorkerHandle {
sender: tx,
receiver: rx,
}
}
/// Loads the chunks around the spawn area and creates
/// a chunk hold on those chunks to prevent them from
/// being unloaded.
///
/// Note that these chunks are loaded asynchronously,
/// and this function will return before loading is complete.
fn load_spawn_chunks(game: &mut Game, world: &mut World) {
let view_distance = i32::from(game.config.server.view_distance);
// Create an entity for the server and
// add chunk holders using it.
let server_entity = EntityBuilder::new().build().spawn_in(world);
let offset_x = game.level.spawn_x / 16;
let offset_z = game.level.spawn_z / 16;
for x in -view_distance..=view_distance {
for z in -view_distance..=view_distance {
let chunk = ChunkPosition::new(x + offset_x, z + offset_z);
chunk_logic::load_chunk(&game.chunk_worker_handle, chunk);
game.chunk_holders.insert_holder(chunk, server_entity);
}
}
}
/// Loads the configuration file, creating a default
/// one if it does not exist.
fn load_config() -> Config {
match config::load_from_file("feather.toml") {
Ok(config) => config,
Err(e) => match e {
config::ConfigError::Io(_) => {
// Use default config
println!("Config not found - creating it");
let config = Config::default();
let mut file = File::create("feather.toml").unwrap();
file.write_all(config::DEFAULT_CONFIG_STR.as_bytes())
.unwrap();
config
}
config::ConfigError::Parse(e) => {
panic!("Failed to load configuration file: {}", e);
}
},
}
}
/// Starts the IO threads.
fn init_io_manager(
config: Arc<Config>,
player_count: Arc<AtomicU32>,
server_icon: Arc<Option<String>>,
packet_buffers: Arc<PacketBuffers>,
) -> io::NetworkIoManager {
io::NetworkIoManager::start(
format!("{}:{}", config.server.address, config.server.port)
.parse()
.unwrap(),
config,
player_count,
server_icon,
packet_buffers,
)
}
fn init_log(config: &Config) {
let level = match config.log.level.as_str() {
"trace" => log::Level::Trace,
"debug" => log::Level::Debug,
"info" => log::Level::Info,
"warn" => log::Level::Warn,
"error" => log::Level::Error,
_ => panic!("Unknown log level {}", config.log.level),
};
simple_logger::init_with_level(level).unwrap();
}
fn create_level(config: &Config) -> LevelData {
let seed = get_seed(config);
let world_name = &config.world.name;
debug!("Using seed {} for world '{}'", seed, world_name);
// TODO: Generate spawn position properly
LevelData {
allow_commands: false,
border_center_x: 0.0,
border_center_z: 0.0,
border_damage_per_block: 0.0,
border_safe_zone: 0.0,
border_size: 0.0,
clear_weather_time: 0,
data_version: 0,
day_time: 0,
difficulty: 0,
difficulty_locked: 0,
game_type: 0,
hardcore: false,
initialized: false,
last_played: 0,
raining: false,
rain_time: 0,
seed,
spawn_x: 0,
spawn_y: 100,
spawn_z: 0,
thundering: false,
thunder_time: 0,
time: 0,
version: Default::default(),
generator_name: config.world.generator.to_string(),
generator_options: None,
}
}
fn get_seed(config: &Config) -> i64 {
let seed_raw = &config.world.seed;
// Empty seed: random
// Seed is valid i64: parse
// Seed is something else: hash
if seed_raw.is_empty() {
rand::thread_rng().gen()
} else {
match seed_raw.parse::<i64>() {
Ok(seed_int) => seed_int,
Err(_) => hash_seed(seed_raw.as_str()),
}
}
}
fn hash_seed(seed_raw: &str) -> i64 {
let mut hasher = DefaultHasher::new();
seed_raw.hash(&mut hasher);
hasher.finish() as i64
}
/// Loads the level.dat file for the world.
fn load_level(path: &Path) -> anyhow::Result<LevelData> {
let file = File::open(path)?;
let data = deserialize_level_file(file)?;
Ok(data)
}
/// Tries to load a server icon from the current directory.
fn load_server_icon() -> Option<String> {
let icon_file: Option<File> = match File::open("server-icon.png") {
Ok(file) => Some(file),
Err(_) => None,
};
let mut icon_file = icon_file?;
let mut data = Vec::new();
if icon_file.read_to_end(&mut data).is_err() {
warn!("Failed to load server icon.");
return None;
}
let b64_icon = base64::encode(&data);
Some(format!("data:image/png;base64,{}", b64_icon))
}
/// Retrieves the current time in seconds
/// since the UNIX epoch.
pub fn current_time_in_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
/// Retrieves the current time in milliseconds
/// since the UNIX epoch.
pub fn current_time_in_millis() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64
}