Skip to main content

veloren_server/persistence/
character_loader.rs

1use crate::persistence::{
2    ConnectionMode, DatabaseSettings, PersistedComponents,
3    character::{load_character_data, load_character_list},
4    error::PersistenceError,
5    establish_connection,
6};
7use common::{
8    character::{CharacterId, CharacterItem},
9    event::UpdateCharacterMetadata,
10};
11use crossbeam_channel::{self, TryIter};
12use rusqlite::Connection;
13use std::sync::{Arc, RwLock};
14use tracing::{debug, error};
15use vek::Vec3;
16
17type CharacterList = Vec<CharacterItem<Vec3<f32>>>;
18pub(crate) type CharacterListResult = Result<CharacterList, PersistenceError>;
19pub(crate) type CharacterCreationResult = Result<(CharacterId, CharacterList), PersistenceError>;
20pub(crate) type CharacterEditResult = Result<(CharacterId, CharacterList), PersistenceError>;
21pub(crate) type CharacterDataResult =
22    Result<(PersistedComponents, UpdateCharacterMetadata), PersistenceError>;
23type CharacterLoaderRequest = (specs::Entity, CharacterLoaderRequestKind);
24
25/// Available database operations when modifying a player's character list
26enum CharacterLoaderRequestKind {
27    LoadCharacterList {
28        player_uuid: String,
29    },
30    LoadCharacterData {
31        player_uuid: String,
32        character_id: CharacterId,
33    },
34}
35
36#[derive(Debug)]
37pub enum CharacterUpdaterMessage {
38    CharacterScreenResponse(CharacterScreenResponse),
39    DatabaseBatchCompletion(u64),
40}
41
42/// An event emitted from CharacterUpdater in response to a request made from
43/// the character selection/editing screen
44#[derive(Debug)]
45pub struct CharacterScreenResponse {
46    pub target_entity: specs::Entity,
47    pub response_kind: CharacterScreenResponseKind,
48}
49
50impl CharacterScreenResponse {
51    pub fn is_err(&self) -> bool {
52        matches!(
53            &self.response_kind,
54            CharacterScreenResponseKind::CharacterData(box Err(_))
55                | CharacterScreenResponseKind::CharacterList(Err(_))
56                | CharacterScreenResponseKind::CharacterCreation(Err(_))
57        )
58    }
59}
60
61#[derive(Debug)]
62pub enum CharacterScreenResponseKind {
63    CharacterList(CharacterListResult),
64    CharacterData(Box<CharacterDataResult>),
65    CharacterCreation(CharacterCreationResult),
66    CharacterEdit(CharacterEditResult),
67}
68
69/// A bi-directional messaging resource for making requests to modify or load
70/// character data in a background thread.
71///
72/// This is used on the character selection screen, and after character
73/// selection when loading the components associated with a character.
74///
75/// Requests messages are sent in the form of
76/// [`CharacterLoaderRequestKind`] and are dispatched at the character select
77/// screen.
78///
79/// Responses are polled on each server tick in the format
80/// [`CharacterUpdaterMessage`]
81pub struct CharacterLoader {
82    update_rx: crossbeam_channel::Receiver<CharacterUpdaterMessage>,
83    update_tx: crossbeam_channel::Sender<CharacterLoaderRequest>,
84}
85
86impl CharacterLoader {
87    pub fn new(settings: Arc<RwLock<DatabaseSettings>>) -> Result<Self, PersistenceError> {
88        let (update_tx, internal_rx) = crossbeam_channel::unbounded::<CharacterLoaderRequest>();
89        let (internal_tx, update_rx) = crossbeam_channel::unbounded::<CharacterUpdaterMessage>();
90
91        let builder = std::thread::Builder::new().name("persistence_loader".into());
92        builder
93            .spawn(move || {
94                // Unwrap here is safe as there is no code that can panic when the write lock is
95                // taken that could cause the RwLock to become poisoned.
96                //
97                // This connection -must- remain read-only to avoid lock contention with the
98                // CharacterUpdater thread.
99                let mut conn =
100                    establish_connection(&settings.read().unwrap(), ConnectionMode::ReadOnly);
101
102                for request in internal_rx {
103                    conn.update_log_mode(&settings);
104
105                    let response = CharacterLoader::process_request(request, &conn);
106                    debug!("Processing complete");
107                    if let Err(e) = internal_tx.send(response) {
108                        error!(?e, "Could not send character loader response");
109                    }
110                    debug!("Sent character loader response");
111                }
112            })
113            .unwrap();
114
115        Ok(Self {
116            update_rx,
117            update_tx,
118        })
119    }
120
121    // TODO: Refactor the way that we send errors to the client to not require a
122    // specific Result type per CharacterScreenResponseKind, and remove
123    // CharacterScreenResponse::is_err()
124    fn process_request(
125        request: CharacterLoaderRequest,
126        connection: &Connection,
127    ) -> CharacterUpdaterMessage {
128        let (entity, kind) = request;
129        CharacterUpdaterMessage::CharacterScreenResponse(CharacterScreenResponse {
130            target_entity: entity,
131            response_kind: match kind {
132                CharacterLoaderRequestKind::LoadCharacterList { player_uuid } => {
133                    debug!(?player_uuid, "Loading character list");
134                    CharacterScreenResponseKind::CharacterList(load_character_list(
135                        &player_uuid,
136                        connection,
137                    ))
138                },
139                CharacterLoaderRequestKind::LoadCharacterData {
140                    player_uuid,
141                    character_id,
142                } => {
143                    debug!(?player_uuid, ?character_id, "Loading character data");
144                    let result = load_character_data(player_uuid, character_id, connection);
145                    if result.is_err() {
146                        error!(
147                            ?result,
148                            "Error loading character data for character_id: {}", character_id.0
149                        );
150                    }
151                    CharacterScreenResponseKind::CharacterData(Box::new(result))
152                },
153            },
154        })
155    }
156
157    /// Loads a list of characters belonging to the player identified by
158    /// `player_uuid`
159    pub fn load_character_list(&self, entity: specs::Entity, player_uuid: String) {
160        debug!(?player_uuid, "Requesting character list");
161        if let Err(e) = self
162            .update_tx
163            .send((entity, CharacterLoaderRequestKind::LoadCharacterList {
164                player_uuid,
165            }))
166        {
167            error!(?e, "Could not send character list load request");
168        }
169    }
170
171    /// Loads components associated with a character
172    pub fn load_character_data(
173        &self,
174        entity: specs::Entity,
175        player_uuid: String,
176        character_id: CharacterId,
177    ) {
178        debug!(?character_id, ?player_uuid, "Requesting character data");
179        if let Err(e) =
180            self.update_tx
181                .send((entity, CharacterLoaderRequestKind::LoadCharacterData {
182                    player_uuid,
183                    character_id,
184                }))
185        {
186            error!(?e, "Could not send character data load request");
187        }
188    }
189
190    /// Returns a non-blocking iterator over CharacterUpdaterMessage messages
191    pub fn messages(&self) -> TryIter<'_, CharacterUpdaterMessage> { self.update_rx.try_iter() }
192}