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
use super::{
    packet::{CompPacket, CompSyncPackage, CompUpdateKind, EntityPackage, EntitySyncPackage},
    track::UpdateTracker,
};
use common::{
    resources::PlayerEntity,
    uid::{IdMaps, Uid},
};
use specs::{world::Builder, WorldExt};
use tracing::error;

pub trait WorldSyncExt {
    fn register_sync_marker(&mut self);
    fn register_synced<C: specs::Component + Clone + Send + Sync>(&mut self)
    where
        C::Storage: Default + specs::storage::Tracked;
    fn register_tracker<C: specs::Component + Clone + Send + Sync>(&mut self)
    where
        C::Storage: Default + specs::storage::Tracked;
    fn create_entity_synced(&mut self) -> specs::EntityBuilder;
    fn delete_entity_and_clear_uid_mapping(&mut self, uid: Uid);
    fn uid_from_entity(&self, entity: specs::Entity) -> Option<Uid>;
    fn entity_from_uid(&self, uid: Uid) -> Option<specs::Entity>;
    fn apply_entity_package<P: CompPacket>(
        &mut self,
        entity_package: EntityPackage<P>,
    ) -> specs::Entity;
    fn apply_entity_sync_package(&mut self, package: EntitySyncPackage, client_uid: Option<Uid>);
    fn apply_comp_sync_package<P: CompPacket>(&mut self, package: CompSyncPackage<P>);
}

impl WorldSyncExt for specs::World {
    fn register_sync_marker(&mut self) {
        self.register_synced::<Uid>();
        self.insert(IdMaps::new());
    }

    fn register_synced<C: specs::Component + Clone + Send + Sync>(&mut self)
    where
        C::Storage: Default + specs::storage::Tracked,
    {
        self.register::<C>();
        self.register_tracker::<C>();
    }

    fn register_tracker<C: specs::Component + Clone + Send + Sync>(&mut self)
    where
        C::Storage: Default + specs::storage::Tracked,
    {
        let tracker = UpdateTracker::<C>::new(self);
        self.insert(tracker);
    }

    fn create_entity_synced(&mut self) -> specs::EntityBuilder {
        // TODO: Add metric for number of new entities created in a tick? Most
        // convenient would be to store counter in `IdMaps` so that we don't
        // have to fetch another resource nor require an additional parameter here
        // nor use globals.
        let builder = self.create_entity();
        let uid = builder
            .world
            .write_resource::<IdMaps>()
            .allocate(builder.entity);
        builder.with(uid)
    }

    /// This method should be used from the client-side when processing network
    /// messages that delete entities.
    ///
    /// Only used on the client.
    fn delete_entity_and_clear_uid_mapping(&mut self, uid: Uid) {
        // Clear from uid allocator
        let maybe_entity = self.write_resource::<IdMaps>()
                // Note, rtsim entity and character id mappings don't exist on the client but it is
                // easier to reuse the same structure here since there is shared code that needs to
                // lookup the entity from a uid.
                .remove_entity(None, Some(uid), None, None);
        if let Some(entity) = maybe_entity {
            if let Err(e) = self.delete_entity(entity) {
                error!(?e, "Failed to delete entity");
            }
        }
    }

    /// Get the UID of an entity
    fn uid_from_entity(&self, entity: specs::Entity) -> Option<Uid> {
        self.read_storage::<Uid>().get(entity).copied()
    }

    /// Get an entity from a UID
    fn entity_from_uid(&self, uid: Uid) -> Option<specs::Entity> {
        self.read_resource::<IdMaps>().uid_entity(uid)
    }

    fn apply_entity_package<P: CompPacket>(
        &mut self,
        entity_package: EntityPackage<P>,
    ) -> specs::Entity {
        let EntityPackage { uid, comps } = entity_package;

        let entity = create_entity_with_uid(self, uid);
        for packet in comps {
            packet.apply_insert(entity, self, true)
        }

        entity
    }

    fn apply_entity_sync_package(&mut self, package: EntitySyncPackage, client_uid: Option<Uid>) {
        // Take ownership of the fields
        let EntitySyncPackage {
            created_entities,
            deleted_entities,
        } = package;

        // Attempt to create entities
        created_entities.into_iter().for_each(|uid| {
            create_entity_with_uid(self, uid);
        });

        // Attempt to delete entities that were marked for deletion
        deleted_entities.into_iter().for_each(|uid| {
            // Skip deleting the client's own entity. It will appear here when exiting
            // "in-game" and the client already handles cleaning things up
            // there. Although the client might not get this anyway since it
            // should no longer be subscribed to any regions.
            if client_uid != Some(uid) {
                self.delete_entity_and_clear_uid_mapping(uid);
            }
        });
    }

    fn apply_comp_sync_package<P: CompPacket>(&mut self, package: CompSyncPackage<P>) {
        // Update components
        let player_entity = self.read_resource::<PlayerEntity>().0;
        package.comp_updates.into_iter().for_each(|(uid, update)| {
            if let Some(entity) = self.read_resource::<IdMaps>().uid_entity(uid.into()) {
                let force_update = player_entity == Some(entity);
                match update {
                    CompUpdateKind::Inserted(packet) => {
                        packet.apply_insert(entity, self, force_update);
                    },
                    CompUpdateKind::Modified(packet) => {
                        packet.apply_modify(entity, self, force_update);
                    },
                    CompUpdateKind::Removed(phantom) => {
                        P::apply_remove(phantom, entity, self);
                    },
                }
            }
        });
    }
}

// Private utilities
//
// Only used on the client.
fn create_entity_with_uid(specs_world: &mut specs::World, entity_uid: Uid) -> specs::Entity {
    let existing_entity = specs_world.read_resource::<IdMaps>().uid_entity(entity_uid);

    // TODO: Are there any expected cases where there is an existing entity with
    // this UID? If not, we may want to log an error. Otherwise, it may be useful to
    // document these cases.
    match existing_entity {
        Some(entity) => entity,
        None => {
            let entity_builder = specs_world.create_entity();
            entity_builder
                .world
                .write_resource::<IdMaps>()
                .add_entity(entity_uid, entity_builder.entity);
            entity_builder.with(entity_uid).build()
        },
    }
}