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