forked from feather-rs/feather
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsave.rs
More file actions
224 lines (196 loc) · 6.22 KB
/
save.rs
File metadata and controls
224 lines (196 loc) · 6.22 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
//! Handles saving of chunks and entities
use crate::{chunk_manager, ChunkWorkerHandle};
use feather_core::anvil::entity::{BaseEntityData, EntityData};
use feather_core::anvil::{
block_entity::BlockEntityData,
player::{InventorySlot, PlayerData},
};
use feather_core::inventory::{Inventory, Window};
use feather_core::util::{ChunkPosition, Gamemode, Position, Vec3d};
use feather_server_types::{
tasks, BlockSerializer, ChunkLoadEvent, ChunkUnloadEvent, ComponentSerializer, Game, Health,
PlayerLeaveEvent, Uuid, TICK_LENGTH, TPS,
};
use fecs::{Entity, World};
use std::collections::VecDeque;
use std::path::Path;
use std::sync::Arc;
/// A chunk to save + the tick count at which to do so.
#[derive(Clone, Copy, Debug)]
struct SaveTask {
/// Chunk position to save.
chunk: ChunkPosition,
/// Tick count at which to save this chunk.
at: u64,
}
/// Queue of chunks to save.
#[derive(Debug, Default)]
struct SaveQueue(VecDeque<SaveTask>);
/// On a chunk load, adds the chunk to the save queue.
#[fecs::event_handler]
pub fn on_chunk_load_queue_for_saving(
event: &ChunkLoadEvent,
game: &mut Game,
#[default] save_queue: &mut SaveQueue,
) {
queue_for_saving(game, save_queue, event.chunk);
}
/// On a chunk unload, saves the chunk first.
#[fecs::event_handler]
pub fn on_chunk_unload_save_chunk(
event: &ChunkUnloadEvent,
game: &mut Game,
world: &mut World,
chunk_worker_handle: &ChunkWorkerHandle,
) {
save_chunk_at(game, world, event.chunk, chunk_worker_handle);
}
fn queue_for_saving(game: &mut Game, save_queue: &mut SaveQueue, chunk: ChunkPosition) {
let tick_to_save_at =
game.tick_count + (game.config.world.save_interval.as_millis() as u64) / TICK_LENGTH;
let task = SaveTask {
chunk,
at: tick_to_save_at,
};
save_queue.0.push_back(task);
}
/// System which checks for chunks which have been queued for saving
/// and, if it is time, saves them.
#[fecs::system]
pub fn chunk_save(
game: &mut Game,
world: &mut World,
save_queue: &mut SaveQueue,
chunk_worker_handle: &ChunkWorkerHandle,
) {
// no need to run this system every tick
if game.tick_count % TPS != 0 {
return;
}
loop {
let task = match save_queue.0.front().copied() {
Some(task) => task,
None => return, // no save tasks to run
};
if game.chunk_map.chunk_at(task.chunk).is_none() {
save_queue
.0
.pop_front()
.expect("we just verified the front task exists");
continue;
}
if task.at <= game.tick_count {
// Save the chunk, then pop the task from the queue.
save_chunk_at(game, world, task.chunk, chunk_worker_handle);
save_queue
.0
.pop_front()
.expect("we just verified the front task exists");
// Requeue the chunk for saving again.
queue_for_saving(game, save_queue, task.chunk);
} else {
return;
}
}
}
pub fn save_chunk_at(
game: &Game,
world: &World,
pos: ChunkPosition,
chunk_worker_handle: &ChunkWorkerHandle,
) {
let chunk = game
.chunk_map
.chunk_handle_at(pos)
.expect("chunk does not exist");
if !chunk.write().check_modified() && game.chunk_entities.entities_in_chunk(pos).is_empty() {
return;
}
// Serialize the entities in the chunk.
let (entities, block_entities) = serialize_entities(game, world, pos);
log::trace!("Queuing chunk at {} for saving", pos);
chunk_manager::save_chunk(
chunk_worker_handle,
game.chunk_map.chunk_handle_at(pos).unwrap(),
entities.collect(),
block_entities.collect(),
);
}
fn serialize_entities<'a>(
game: &'a Game,
world: &'a World,
pos: ChunkPosition,
) -> (
impl Iterator<Item = EntityData> + 'a,
impl Iterator<Item = BlockEntityData> + 'a,
) {
let entities = game
.chunk_entities
.entities_in_chunk(pos)
.iter()
.filter_map(move |entity| {
if let Some(serializer) = world.try_get::<ComponentSerializer>(*entity) {
let accessor = world.entity(*entity).expect("entity does not exist");
Some(serializer.serialize(game, &accessor))
} else {
None
}
});
let block_entities = game
.chunk_entities
.entities_in_chunk(pos)
.iter()
.filter_map(move |entity| {
if let Some(serializer) = world.try_get::<BlockSerializer>(*entity) {
let accessor = world.entity(*entity).expect("entity does not exist");
Some(serializer.serialize(game, &accessor))
} else {
None
}
});
(entities, block_entities)
}
#[fecs::event_handler]
pub fn on_player_leave_save_data(event: &PlayerLeaveEvent, game: &Game, world: &mut World) {
save_player_data(game, world, event.player);
}
pub fn save_player_data(game: &Game, world: &World, player: Entity) {
let inventory = world
.get::<Inventory>(player)
.enumerate()
.filter_map(|(index, slot)| slot.map(move |slot| (index, slot)))
.filter_map(|(index, slot)| {
InventorySlot::from_network_index(
Window::player(player).convert_slot(index, player).unwrap(),
slot,
)
})
.collect();
let health = world
.try_get::<Health>(player)
.map(|health| health.0 as f32)
.unwrap_or(1.0);
let data = PlayerData {
entity: BaseEntityData::new(
*world.get::<Position>(player),
Vec3d::broadcast(0.0),
health,
),
gamemode: world.get::<Gamemode>(player).id() as i32,
inventory,
};
let uuid = *world.get::<Uuid>(player);
let config = Arc::clone(&game.config);
tasks().spawn(async move {
match feather_core::anvil::player::save_player_data(
&Path::new(&config.world.name),
uuid,
&data,
)
.await
{
Ok(_) => (),
Err(e) => log::error!("Failed to save player data for UUID {}: {}", uuid, e),
}
});
}