Skip to content

Commit

Permalink
encode additional entities compactly
Browse files Browse the repository at this point in the history
  • Loading branch information
RJ committed Oct 4, 2023
1 parent 66dfdab commit bcb29c2
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 16 deletions.
41 changes: 36 additions & 5 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,7 @@ fn deserialize_component_diffs(
) -> Result<(), bincode::Error> {
let entities_count: u16 = bincode::deserialize_from(&mut *cursor)?;
for _ in 0..entities_count {
let entity = deserialize_entity(&mut *cursor)?;
let predicted_entity = match deserialize_entity(&mut *cursor)? {
Entity::PLACEHOLDER => None,
e => Some(e),
};
let (entity, predicted_entity) = deserialize_entity_combo(&mut *cursor)?;
let mut entity = entity_map.get_by_server_or_spawn(world, entity, predicted_entity);
let components_count: u8 = bincode::deserialize_from(&mut *cursor)?;
for _ in 0..components_count {
Expand Down Expand Up @@ -212,6 +208,41 @@ fn deserialize_entity(cursor: &mut Cursor<Bytes>) -> Result<Entity, bincode::Err
Ok(Entity::from_bits(bits))
}

/// Deserializes an Entity then an optional second Entity from the cursor.
/// see [`ReplicationBuffer::write_entity_combo()`].
fn deserialize_entity_combo(
cursor: &mut Cursor<Bytes>,
) -> Result<(Entity, Option<Entity>), bincode::Error> {
let flagged_index: u64 = DefaultOptions::new().deserialize_from(&mut *cursor)?;
let has_generation = (flagged_index & 1) > 0;
let has_prediction = (flagged_index & 2) > 0;
let has_prediction_generation = (flagged_index & 4) > 0;

let generation = if has_generation {
DefaultOptions::new().deserialize_from(&mut *cursor)?
} else {
0u32
};

let bits = (generation as u64) << 32 | (flagged_index >> 3);
let entity = Entity::from_bits(bits);

let predicted_entity: Option<Entity> = if has_prediction {
let predicted_index: u32 = DefaultOptions::new().deserialize_from(&mut *cursor)?;
let predicted_generation = if has_prediction_generation {
DefaultOptions::new().deserialize_from(&mut *cursor)?
} else {
0u32
};
let bits = (predicted_generation as u64) << 32 | predicted_index as u64;
Some(Entity::from_bits(bits))
} else {
None
};

Ok((entity, predicted_entity))
}

/// Type of component change.
///
/// Parameter for [`deserialize_component_diffs`].
Expand Down
7 changes: 5 additions & 2 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,10 @@ fn collect_changes(
let predicted_entity = predictions
.get_predicted_entity(buffer.client_id(), archetype_entity.entity())
.copied();
buffer.start_entity_data(archetype_entity.entity(), predicted_entity);
// even if predicted_entity is none, we have to send the None, since the client is
// configured to expect prediction data to be encoded.
buffer
.start_entity_data_with_prediction(archetype_entity.entity(), predicted_entity);
}

for component_id in archetype.components() {
Expand Down Expand Up @@ -332,7 +335,7 @@ fn collect_removals(

for (entity, removal_tracker) in removal_trackers {
for buffer in &mut *buffers {
buffer.start_entity_data(entity, None);
buffer.start_entity_data(entity);
for (&replication_id, &tick) in &removal_tracker.0 {
if tick.is_newer_than(buffer.system_tick(), system_tick) {
buffer.write_removal(replication_id)?;
Expand Down
73 changes: 64 additions & 9 deletions src/server/replication_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ pub(super) struct ReplicationBuffer {

/// Entity client told us they spawned as a prediction, that they hope to match up with
/// data_entity, insted of spawning a new one during diff receiving.
data_prediction_entity: Option<Entity>,
data_entity_prediction: Option<Entity>,

/// Does this data_entity potentially have a client-predicted entity associated?
/// This could be true when data_entity_prediction is None, when we are supporting predictions
/// but there is no prediction for this entity (we have to transmit the none in that case).
data_entity_send_prediction: bool,
}

impl ReplicationBuffer {
Expand All @@ -72,7 +77,8 @@ impl ReplicationBuffer {
entity_data_pos: Default::default(),
entity_data_len: Default::default(),
data_entity: Entity::PLACEHOLDER,
data_prediction_entity: None,
data_entity_prediction: None,
data_entity_send_prediction: false,
})
}

Expand Down Expand Up @@ -152,24 +158,35 @@ impl ReplicationBuffer {
/// Entity will be written lazily after first data write and its position will be remembered to write length later.
/// See also [`Self::end_entity_data`], [`Self::write_current_entity`], [`Self::write_change`]
/// and [`Self::write_removal`].
pub(super) fn start_entity_data(&mut self, entity: Entity, predicted_entity: Option<Entity>) {
pub(super) fn start_entity_data(&mut self, entity: Entity) {
debug_assert_eq!(self.entity_data_len, 0);

self.data_entity = entity;
self.data_entity_send_prediction = false;
self.entity_data_pos = self.message.position();
}

pub(super) fn start_entity_data_with_prediction(
&mut self,
entity: Entity,
predicted_entity: Option<Entity>,
) {
debug_assert_eq!(self.entity_data_len, 0);

self.data_entity = entity;
self.data_entity_send_prediction = true;
self.data_entity_prediction = predicted_entity;
self.entity_data_pos = self.message.position();
self.data_prediction_entity = predicted_entity;
}

/// Writes entity for current data and updates remembered position for it to write length later.
///
/// Should be called only after first data write.
fn write_data_entity(&mut self) -> Result<(), bincode::Error> {
self.write_entity(self.data_entity)?;
if let Some(pred_entity) = self.data_prediction_entity {
self.write_entity(pred_entity)?;
if self.data_entity_send_prediction {
self.write_entity_combo(self.data_entity, self.data_entity_prediction)?;
} else {
// temporary hack until i decide on format
self.write_entity(Entity::PLACEHOLDER)?;
self.write_entity(self.data_entity)?;
}
self.entity_data_pos = self.message.position();
self.message
Expand Down Expand Up @@ -276,6 +293,44 @@ impl ReplicationBuffer {
Ok(())
}

/// Serializes `entity` and `predicted_entity`, similar to write_entity.
///
/// The index is first shifted left by 3, to make room for the three flags:
///
/// 001 | generation_flag: does our entity have a generation > 0
/// 010 | prediction_flag: is there an associated predicted entity
/// 100 | prediction_generation_flag: does any predicted entity have a generation > 0
fn write_entity_combo(
&mut self,
entity: Entity,
optional_entity: Option<Entity>,
) -> Result<(), bincode::Error> {
let mut flagged_index = (entity.index() as u64) << 3;

let generation_flag = entity.generation() > 0;
let prediction_flag = optional_entity.is_some();
let prediction_generation_flag = optional_entity.map_or(false, |e| e.generation() > 0);

flagged_index |= generation_flag as u64;
flagged_index |= (prediction_flag as u64) << 1;
flagged_index |= (prediction_generation_flag as u64) << 2;

DefaultOptions::new().serialize_into(&mut self.message, &flagged_index)?;
if generation_flag {
DefaultOptions::new().serialize_into(&mut self.message, &entity.generation())?;
}
if prediction_flag {
DefaultOptions::new()
.serialize_into(&mut self.message, &optional_entity.unwrap().index())?;
if prediction_generation_flag {
DefaultOptions::new()
.serialize_into(&mut self.message, &optional_entity.unwrap().generation())?;
}
}

Ok(())
}

/// Send the buffer contents into a renet server channel.
///
/// [`Self::reset`] should be called after it to use this buffer again.
Expand Down

0 comments on commit bcb29c2

Please sign in to comment.