Get linear interpolation working for {Pos,Vel,Ori} with client-side timestamps.

This commit is contained in:
Avi Weinstock 2021-03-13 17:15:19 -05:00
parent a71bacdce0
commit dad0012973
8 changed files with 211 additions and 28 deletions

View File

@ -33,6 +33,7 @@ use common::{
grid::Grid,
outcome::Outcome,
recipe::RecipeBook,
resources::PlayerEntity,
terrain::{block::Block, neighbors, BiomeKind, SitesKind, TerrainChunk, TerrainChunkSize},
trade::{PendingTrade, TradeAction, TradeId, TradeResult},
uid::{Uid, UidAllocator},
@ -281,6 +282,7 @@ impl Client {
let entity = state.ecs_mut().apply_entity_package(entity_package);
*state.ecs_mut().write_resource() = time_of_day;
*state.ecs_mut().write_resource() = PlayerEntity(Some(entity));
state.ecs_mut().insert(material_stats);
state.ecs_mut().insert(ability_map);

View File

@ -101,8 +101,8 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::CharacterState(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Pos(comp) => sync::handle_interp_insert(comp, entity, world),
EcsCompPacket::Vel(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Ori(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Vel(comp) => sync::handle_interp_insert(comp, entity, world),
EcsCompPacket::Ori(comp) => sync::handle_interp_insert(comp, entity, world),
EcsCompPacket::Shockwave(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::BeamSegment(comp) => sync::handle_insert(comp, entity, world),
}
@ -133,8 +133,8 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::CharacterState(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Pos(comp) => sync::handle_interp_modify(comp, entity, world),
EcsCompPacket::Vel(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Ori(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Vel(comp) => sync::handle_interp_modify(comp, entity, world),
EcsCompPacket::Ori(comp) => sync::handle_interp_modify(comp, entity, world),
EcsCompPacket::Shockwave(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::BeamSegment(comp) => sync::handle_modify(comp, entity, world),
}
@ -169,8 +169,8 @@ impl sync::CompPacket for EcsCompPacket {
sync::handle_remove::<comp::CharacterState>(entity, world)
},
EcsCompPhantom::Pos(_) => sync::handle_interp_remove::<comp::Pos>(entity, world),
EcsCompPhantom::Vel(_) => sync::handle_remove::<comp::Vel>(entity, world),
EcsCompPhantom::Ori(_) => sync::handle_remove::<comp::Ori>(entity, world),
EcsCompPhantom::Vel(_) => sync::handle_interp_remove::<comp::Vel>(entity, world),
EcsCompPhantom::Ori(_) => sync::handle_interp_remove::<comp::Ori>(entity, world),
EcsCompPhantom::Shockwave(_) => sync::handle_remove::<comp::Shockwave>(entity, world),
EcsCompPhantom::BeamSegment(_) => sync::handle_remove::<comp::Ori>(entity, world),
}

View File

@ -1,26 +1,125 @@
// impls of `InterpolatableComponent` on things defined in `common`, since `common_net` is
// downstream of `common`
use common::comp::{Pos, Vel};
// impls of `InterpolatableComponent` on things defined in `common`, since
// `common_net` is downstream of `common`, and an `InterpolationSystem` that
// applies them
use super::InterpolatableComponent;
use specs::{Component, Entity, World};
use common::comp::{Ori, Pos, Vel};
use specs::Component;
use specs_idvs::IdvStorage;
use vek::Vec3;
use tracing::warn;
use vek::ops::{Lerp, Slerp};
#[derive(Default)]
pub struct PosBuffer(pub [Vec3<f32>; 4]);
#[derive(Debug, Default)]
pub struct InterpBuffer<T> {
pub buf: [(f64, T); 4],
pub i: usize,
}
impl Component for PosBuffer {
impl<T: 'static + Send + Sync> Component for InterpBuffer<T> {
type Storage = IdvStorage<Self>;
}
impl InterpolatableComponent for Pos {
type InterpData = PosBuffer;
type InterpData = InterpBuffer<Pos>;
type ReadData = Vel;
fn interpolate(self, data: &mut Self::InterpData, entity: Entity, world: &World) -> Self {
for i in 0..data.0.len()-1 {
data.0[i] = data.0[i+1];
fn update_component(&self, interp_data: &mut Self::InterpData, time: f64) {
let InterpBuffer {
ref mut buf,
ref mut i,
} = interp_data;
*i += 1;
*i %= buf.len();
buf[*i] = (time, *self);
}
fn interpolate(self, interp_data: &Self::InterpData, t2: f64, _vel: &Vel) -> Self {
// lerp to test interface, do hermite spline later
let InterpBuffer { ref buf, ref i } = interp_data;
let (t0, p0) = buf[(i + buf.len() - 1) % buf.len()];
let (t1, p1) = buf[i % buf.len()];
if (t1 - t0).abs() < f64::EPSILON {
return self;
}
data.0[data.0.len()-1] = self.0;
self
let lerp_factor = 1.0 + ((t2 - t1) / (t1 - t0)) as f32;
let mut out = Lerp::lerp_unclamped(p0.0, p1.0, lerp_factor);
if out.map(|x| x.is_nan()).reduce_or() {
warn!(
"interpolation output is nan: {}, {}, {:?}",
t2, lerp_factor, buf
);
out = p1.0;
}
Pos(out)
}
}
impl InterpolatableComponent for Vel {
type InterpData = InterpBuffer<Vel>;
type ReadData = ();
fn update_component(&self, interp_data: &mut Self::InterpData, time: f64) {
let InterpBuffer {
ref mut buf,
ref mut i,
} = interp_data;
*i += 1;
*i %= buf.len();
buf[*i] = (time, *self);
}
fn interpolate(self, interp_data: &Self::InterpData, t2: f64, _: &()) -> Self {
let InterpBuffer { ref buf, ref i } = interp_data;
let (t0, p0) = buf[(i + buf.len() - 1) % buf.len()];
let (t1, p1) = buf[i % buf.len()];
if (t1 - t0).abs() < f64::EPSILON {
return self;
}
let lerp_factor = 1.0 + ((t2 - t1) / (t1 - t0)) as f32;
let mut out = Lerp::lerp_unclamped(p0.0, p1.0, lerp_factor);
if out.map(|x| x.is_nan()).reduce_or() {
warn!(
"interpolation output is nan: {}, {}, {:?}",
t2, lerp_factor, buf
);
out = p1.0;
}
Vel(out)
}
}
impl InterpolatableComponent for Ori {
type InterpData = InterpBuffer<Ori>;
type ReadData = ();
fn update_component(&self, interp_data: &mut Self::InterpData, time: f64) {
let InterpBuffer {
ref mut buf,
ref mut i,
} = interp_data;
*i += 1;
*i %= buf.len();
buf[*i] = (time, *self);
}
fn interpolate(self, interp_data: &Self::InterpData, t2: f64, _: &()) -> Self {
let InterpBuffer { ref buf, ref i } = interp_data;
let (t0, p0) = buf[(i + buf.len() - 1) % buf.len()];
let (t1, p1) = buf[i % buf.len()];
if (t1 - t0).abs() < f64::EPSILON {
return self;
}
let lerp_factor = 1.0 + ((t2 - t1) / (t1 - t0)) as f32;
let mut out = Slerp::slerp_unclamped(p0.0, p1.0, lerp_factor);
if out.into_vec4().map(|x| x.is_nan()).reduce_or() {
warn!(
"interpolation output is nan: {}, {}, {:?}",
t2, lerp_factor, buf
);
out = p1.0;
}
Ori(out.normalized())
}
}

View File

@ -1,5 +1,5 @@
use super::track::UpdateTracker;
use common::uid::Uid;
use common::{resources::Time, uid::Uid};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use specs::{Component, Entity, Join, ReadStorage, World, WorldExt};
use std::{
@ -9,6 +9,10 @@ use std::{
};
use tracing::error;
// TODO: apply_{insert,modify,remove} all take the entity and call
// `write_storage` once per entity per component, instead of once per update
// batch(e.g. in a system-like memory access pattern); if sync ends up being a
// bottleneck, try optimizing this
/// Implemented by type that carries component data for insertion and
/// modification The assocatied `Phantom` type only carries information about
/// which component type is of interest and is used to transmit deletion events
@ -44,13 +48,16 @@ pub fn handle_remove<C: Component>(entity: Entity, world: &World) {
pub trait InterpolatableComponent: Component {
type InterpData: Component + Default;
type ReadData;
fn interpolate(self, data: &mut Self::InterpData, entity: Entity, world: &World) -> Self;
fn update_component(&self, data: &mut Self::InterpData, time: f64);
fn interpolate(self, data: &Self::InterpData, time: f64, read_data: &Self::ReadData) -> Self;
}
pub fn handle_interp_insert<C: InterpolatableComponent>(comp: C, entity: Entity, world: &World) {
let mut interp_data = C::InterpData::default();
let comp = comp.interpolate(&mut interp_data, entity, world);
let time = world.read_resource::<Time>().0;
comp.update_component(&mut interp_data, time);
handle_insert(comp, entity, world);
handle_insert(interp_data, entity, world);
}
@ -61,7 +68,8 @@ pub fn handle_interp_modify<C: InterpolatableComponent + Debug>(
world: &World,
) {
if let Some(mut interp_data) = world.write_storage::<C::InterpData>().get_mut(entity) {
let comp = comp.interpolate(&mut interp_data, entity, world);
let time = world.read_resource::<Time>().0;
comp.update_component(&mut interp_data, time);
handle_modify(comp, entity, world);
} else {
error!(

View File

@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize};
use specs::Entity;
/// A resource that stores the time of day.
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Default)]
@ -26,3 +27,8 @@ pub enum GameMode {
// To be used later when we no longer start up an entirely new server for singleplayer
Singleplayer,
}
/// A resource that stores the player's entity (on the client), and None on the
/// server
#[derive(Copy, Clone, Default, Debug)]
pub struct PlayerEntity(pub Option<Entity>);

View File

@ -0,0 +1,63 @@
use common::{
comp::{Ori, Pos, Vel},
resources::{PlayerEntity, Time},
};
use common_ecs::{Job, Origin, Phase, System};
use common_net::sync::InterpolatableComponent;
use specs::{
prelude::ParallelIterator, shred::ResourceId, Entities, ParJoin, Read, ReadStorage, SystemData,
World, WriteStorage,
};
#[derive(SystemData)]
pub struct InterpolationSystemData<'a> {
time: Read<'a, Time>,
player: Read<'a, PlayerEntity>,
entities: Entities<'a>,
pos: WriteStorage<'a, Pos>,
pos_interpdata: ReadStorage<'a, <Pos as InterpolatableComponent>::InterpData>,
vel: WriteStorage<'a, Vel>,
vel_interpdata: ReadStorage<'a, <Vel as InterpolatableComponent>::InterpData>,
ori: WriteStorage<'a, Ori>,
ori_interpdata: ReadStorage<'a, <Ori as InterpolatableComponent>::InterpData>,
}
#[derive(Default)]
pub struct InterpolationSystem;
impl<'a> System<'a> for InterpolationSystem {
type SystemData = InterpolationSystemData<'a>;
const NAME: &'static str = "interpolation";
const ORIGIN: Origin = Origin::Common;
const PHASE: Phase = Phase::Apply;
fn run(_job: &mut Job<Self>, mut data: InterpolationSystemData<'a>) {
let time = data.time.0;
let player = data.player.0;
(
&data.entities,
&mut data.pos,
&data.pos_interpdata,
&data.vel,
)
.par_join()
.filter(|(e, _, _, _)| Some(e) != player.as_ref())
.for_each(|(_, pos, interp, vel)| {
*pos = pos.interpolate(interp, time, vel);
});
(&data.entities, &mut data.vel, &data.vel_interpdata)
.par_join()
.filter(|(e, _, _)| Some(e) != player.as_ref())
.for_each(|(_, vel, interp)| {
*vel = vel.interpolate(interp, time, &());
});
(&data.entities, &mut data.ori, &data.ori_interpdata)
.par_join()
.filter(|(e, _, _)| Some(e) != player.as_ref())
.for_each(|(_, ori, interp)| {
*ori = ori.interpolate(interp, time, &());
});
}
}

View File

@ -5,6 +5,7 @@ mod beam;
mod buff;
pub mod character_behavior;
pub mod controller;
mod interpolation;
pub mod melee;
mod mount;
pub mod phys;

View File

@ -6,7 +6,7 @@ use common::{
comp,
event::{EventBus, LocalEvent, ServerEvent},
region::RegionMap,
resources::{DeltaTime, GameMode, Time, TimeOfDay},
resources::{DeltaTime, GameMode, PlayerEntity, Time, TimeOfDay},
terrain::{Block, TerrainChunk, TerrainGrid},
time::DayPeriod,
trade::Trades,
@ -14,8 +14,8 @@ use common::{
vol::{ReadVol, WriteVol},
};
use common_base::span;
use common_ecs::{PhysicsMetrics, SysMetrics};
use common_net::sync::{interpolation, WorldSyncExt};
use common_ecs::{run_now, PhysicsMetrics, SysMetrics};
use common_net::sync::{interpolation as sync_interp, WorldSyncExt};
use hashbrown::{HashMap, HashSet};
use rayon::{ThreadPool, ThreadPoolBuilder};
use specs::{
@ -164,7 +164,9 @@ impl State {
// Register client-local components
// TODO: only register on the client
ecs.register::<comp::LightAnimation>();
ecs.register::<interpolation::PosBuffer>();
ecs.register::<sync_interp::InterpBuffer<comp::Pos>>();
ecs.register::<sync_interp::InterpBuffer<comp::Vel>>();
ecs.register::<sync_interp::InterpBuffer<comp::Ori>>();
// Register server-local components
// TODO: only register on the server
@ -194,6 +196,7 @@ impl State {
// Register unsynced resources used by the ECS.
ecs.insert(Time(0.0));
ecs.insert(DeltaTime(0.0));
ecs.insert(PlayerEntity(None));
ecs.insert(TerrainGrid::new().unwrap());
ecs.insert(BlockChange::default());
ecs.insert(TerrainChanges::default());
@ -438,6 +441,7 @@ impl State {
let mut dispatcher = dispatch_builder.build();
drop(guard);
span!(guard, "run systems");
run_now::<crate::interpolation::InterpolationSystem>(&self.ecs);
dispatcher.dispatch(&self.ecs);
drop(guard);