first implemetations

sync, somehow we miht wanna put the whole clock inside maybe ?, move files
sync, disable Control and replace it by RemoteControl
This commit is contained in:
Marcel Märtens 2021-06-11 14:33:02 +02:00
parent 1e93648081
commit aa5111c265
15 changed files with 472 additions and 150 deletions

View File

@ -28,9 +28,9 @@ use common::{
invite::{InviteKind, InviteResponse},
skills::Skill,
slot::{EquipSlot, InvSlotId, Slot},
CharacterState, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs,
GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent,
MapMarkerChange, UtteranceKind,
CharacterState, ChatMode, CommandGenerator, ControlAction, ControlEvent, Controller,
ControllerInputs, GroupManip, InputKind, InventoryAction, InventoryEvent,
InventoryUpdateEvent, MapMarkerChange, RemoteController, UtteranceKind,
},
event::{EventBus, LocalEvent, UpdateCharacterMetadata},
grid::Grid,
@ -73,7 +73,7 @@ use num::traits::FloatConst;
use rayon::prelude::*;
use specs::Component;
use std::{
collections::{BTreeMap, VecDeque},
collections::VecDeque,
mem,
sync::Arc,
time::{Duration, Instant},
@ -235,6 +235,9 @@ pub struct Client {
// The pending trade the client is involved in, and it's id
pending_trade: Option<(TradeId, PendingTrade, Option<SitePrices>)>,
local_command_gen: CommandGenerator,
next_control: Controller,
network: Option<Network>,
participant: Option<Participant>,
general_stream: Stream,
@ -716,6 +719,9 @@ impl Client {
pending_invites: HashSet::new(),
pending_trade: None,
local_command_gen: CommandGenerator::default(),
next_control: Controller::default(),
network: Some(network),
participant: Some(participant),
general_stream: stream,
@ -841,9 +847,7 @@ impl Client {
| ClientGeneral::Character(_, _)
| ClientGeneral::Spectate(_) => &mut self.character_screen_stream,
//Only in game
ClientGeneral::ControllerInputs(_)
| ClientGeneral::ControlEvent(_)
| ClientGeneral::ControlAction(_)
ClientGeneral::Control(_)
| ClientGeneral::SetViewDistance(_)
| ClientGeneral::BreakBlock(_)
| ClientGeneral::PlaceBlock(_, _)
@ -1032,9 +1036,11 @@ impl Client {
ControlAction::InventoryAction(InventoryAction::Swap(equip, slot)),
),
(Slot::Inventory(inv1), Slot::Inventory(inv2)) => {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
InventoryEvent::Swap(inv1, inv2),
)))
self.next_control
.events
.push(ControlEvent::InventoryEvent(InventoryEvent::Swap(
inv1, inv2,
)));
},
}
}
@ -1044,9 +1050,10 @@ impl Client {
Slot::Equip(equip) => {
self.control_action(ControlAction::InventoryAction(InventoryAction::Drop(equip)))
},
Slot::Inventory(inv) => self.send_msg(ClientGeneral::ControlEvent(
ControlEvent::InventoryEvent(InventoryEvent::Drop(inv)),
)),
Slot::Inventory(inv) => self
.next_control
.events
.push(ControlEvent::InventoryEvent(InventoryEvent::Drop(inv))),
}
}
@ -1059,9 +1066,9 @@ impl Client {
if let TradeAction::Decline = action {
self.pending_trade.take();
}
self.send_msg(ClientGeneral::ControlEvent(
ControlEvent::PerformTradeAction(id, action),
));
self.next_control
.events
.push(ControlEvent::PerformTradeAction(id, action));
}
}
@ -1077,11 +1084,9 @@ impl Client {
(Slot::Equip(equip), slot) | (slot, Slot::Equip(equip)) => self.control_action(
ControlAction::InventoryAction(InventoryAction::Swap(equip, slot)),
),
(Slot::Inventory(inv1), Slot::Inventory(inv2)) => {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
InventoryEvent::SplitSwap(inv1, inv2),
)))
},
(Slot::Inventory(inv1), Slot::Inventory(inv2)) => self.next_control.events.push(
ControlEvent::InventoryEvent(InventoryEvent::SplitSwap(inv1, inv2)),
),
}
}
@ -1090,9 +1095,10 @@ impl Client {
Slot::Equip(equip) => {
self.control_action(ControlAction::InventoryAction(InventoryAction::Drop(equip)))
},
Slot::Inventory(inv) => self.send_msg(ClientGeneral::ControlEvent(
ControlEvent::InventoryEvent(InventoryEvent::SplitDrop(inv)),
)),
Slot::Inventory(inv) => self
.next_control
.events
.push(ControlEvent::InventoryEvent(InventoryEvent::SplitDrop(inv))),
}
}
@ -1104,10 +1110,9 @@ impl Client {
if self.is_dead() {
return;
}
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
InventoryEvent::Pickup(uid),
)));
self.next_control
.events
.push(ControlEvent::InventoryEvent(InventoryEvent::Pickup(uid)));
}
}
@ -1118,7 +1123,7 @@ impl Client {
}
if let Some(uid) = self.state.read_component_copied(npc_entity) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Interact(uid)));
self.next_control.events.push(ControlEvent::Interact(uid));
}
}
@ -1165,7 +1170,7 @@ impl Client {
let (can_craft, required_sprite) = self.can_craft_recipe(recipe, amount);
let has_sprite = required_sprite.map_or(true, |s| Some(s) == craft_sprite.map(|(_, s)| s));
if can_craft && has_sprite {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
self.next_control.events.push(ControlEvent::InventoryEvent(
InventoryEvent::CraftRecipe {
craft_event: CraftEvent::Simple {
recipe: recipe.to_string(),
@ -1174,7 +1179,7 @@ impl Client {
},
craft_sprite: craft_sprite.map(|(pos, _)| pos),
},
)));
));
true
} else {
false
@ -1194,12 +1199,12 @@ impl Client {
pub fn salvage_item(&mut self, slot: InvSlotId, salvage_pos: Vec3<i32>) -> bool {
let is_salvageable = self.can_salvage_item(slot);
if is_salvageable {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
self.next_control.events.push(ControlEvent::InventoryEvent(
InventoryEvent::CraftRecipe {
craft_event: CraftEvent::Salvage(slot),
craft_sprite: Some(salvage_pos),
},
)));
));
}
is_salvageable
}
@ -1241,7 +1246,7 @@ impl Client {
(mod_kind(primary_component), mod_kind(secondary_component))
{
drop(inventories);
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
self.next_control.events.push(ControlEvent::InventoryEvent(
InventoryEvent::CraftRecipe {
craft_event: CraftEvent::ModularWeapon {
primary_component,
@ -1249,7 +1254,7 @@ impl Client {
},
craft_sprite: sprite_pos,
},
)));
));
true
} else {
false
@ -1264,8 +1269,9 @@ impl Client {
slots: Vec<(u32, InvSlotId)>,
sprite_pos: Option<Vec3<i32>>,
) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
InventoryEvent::CraftRecipe {
self.next_control
.events
.push(ControlEvent::InventoryEvent(InventoryEvent::CraftRecipe {
craft_event: CraftEvent::ModularWeaponPrimaryComponent {
toolkind,
material,
@ -1273,8 +1279,7 @@ impl Client {
slots,
},
craft_sprite: sprite_pos,
},
)));
}));
}
fn update_available_recipes(&mut self) {
@ -1301,18 +1306,16 @@ impl Client {
pub fn sites_mut(&mut self) -> &mut HashMap<SiteId, SiteInfoRich> { &mut self.sites }
pub fn enable_lantern(&mut self) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::EnableLantern));
}
pub fn enable_lantern(&mut self) { self.next_control.events.push(ControlEvent::EnableLantern); }
pub fn disable_lantern(&mut self) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::DisableLantern));
self.next_control.events.push(ControlEvent::DisableLantern);
}
pub fn remove_buff(&mut self, buff_id: BuffKind) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::RemoveBuff(
buff_id,
)));
self.next_control
.events
.push(ControlEvent::RemoveBuff(buff_id));
}
pub fn unlock_skill(&mut self, skill: Skill) {
@ -1338,43 +1341,43 @@ impl Client {
pub fn is_trading(&self) -> bool { self.pending_trade.is_some() }
pub fn send_invite(&mut self, invitee: Uid, kind: InviteKind) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InitiateInvite(
invitee, kind,
)))
self.next_control
.events
.push(ControlEvent::InitiateInvite(invitee, kind));
}
pub fn accept_invite(&mut self) {
// Clear invite
self.invite.take();
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InviteResponse(
InviteResponse::Accept,
)));
self.next_control
.events
.push(ControlEvent::InviteResponse(InviteResponse::Accept));
}
pub fn decline_invite(&mut self) {
// Clear invite
self.invite.take();
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InviteResponse(
InviteResponse::Decline,
)));
self.next_control
.events
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
}
pub fn leave_group(&mut self) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip(
GroupManip::Leave,
)));
self.next_control
.events
.push(ControlEvent::GroupManip(GroupManip::Leave));
}
pub fn kick_from_group(&mut self, uid: Uid) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip(
GroupManip::Kick(uid),
)));
self.next_control
.events
.push(ControlEvent::GroupManip(GroupManip::Kick(uid)));
}
pub fn assign_group_leader(&mut self, uid: Uid) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip(
GroupManip::AssignLeader(uid),
)));
self.next_control
.events
.push(ControlEvent::GroupManip(GroupManip::AssignLeader(uid)));
}
pub fn is_riding(&self) -> bool {
@ -1395,11 +1398,11 @@ impl Client {
pub fn mount(&mut self, entity: EcsEntity) {
if let Some(uid) = self.state.read_component_copied(entity) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Mount(uid)));
self.next_control.events.push(ControlEvent::Mount(uid));
}
}
pub fn unmount(&mut self) { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Unmount)); }
pub fn unmount(&mut self) { self.next_control.events.push(ControlEvent::Unmount); }
pub fn respawn(&mut self) {
if self
@ -1409,7 +1412,7 @@ impl Client {
.get(self.entity())
.map_or(false, |h| h.is_dead)
{
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Respawn));
self.next_control.events.push(ControlEvent::Respawn);
}
}
@ -1490,7 +1493,7 @@ impl Client {
}
pub fn utter(&mut self, kind: UtteranceKind) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Utterance(kind)));
self.next_control.events.push(ControlEvent::Utterance(kind));
}
pub fn toggle_sneak(&mut self) {
@ -1542,15 +1545,7 @@ impl Client {
}
fn control_action(&mut self, control_action: ControlAction) {
if let Some(controller) = self
.state
.ecs()
.write_storage::<Controller>()
.get_mut(self.entity())
{
controller.push_action(control_action);
}
self.send_msg(ClientGeneral::ControlAction(control_action));
self.next_control.actions.push(control_action);
}
pub fn view_distance(&self) -> Option<u32> { self.view_distance }
@ -1675,11 +1670,11 @@ impl Client {
)
});
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::ChangeAbility {
self.next_control.events.push(ControlEvent::ChangeAbility {
slot,
auxiliary_key,
new_ability,
}))
});
}
/// Execute a single client tick, handle input and update the game state by
@ -1688,6 +1683,7 @@ impl Client {
&mut self,
inputs: ControllerInputs,
dt: Duration,
total_tick_time: Duration,
add_foreign_systems: impl Fn(&mut DispatcherBuilder),
) -> Result<Vec<Event>, Error> {
span!(_guard, "tick", "Client::tick");
@ -1714,30 +1710,25 @@ impl Client {
// Pass character actions from frontend input to the player's entity.
if self.presence.is_some() {
prof_span!("handle and send inputs");
if let Err(e) = self
self.next_control.inputs = inputs;
let con = std::mem::take(&mut self.next_control);
let rcon = self.local_command_gen.gen(total_tick_time, con);
let commands = self
.state
.ecs()
.write_storage::<Controller>()
.write_storage::<RemoteController>()
.entry(self.entity())
.map(|entry| {
entry
.or_insert_with(|| Controller {
inputs: inputs.clone(),
queued_inputs: BTreeMap::new(),
events: Vec::new(),
actions: Vec::new(),
})
.inputs = inputs.clone();
})
{
let entry = self.entity();
error!(
?e,
?entry,
"Couldn't access controller component on client entity"
);
}
self.send_msg_err(ClientGeneral::ControllerInputs(Box::new(inputs)))?;
.map(|rc| {
let rc = rc.or_insert_with(RemoteController::default);
rc.push(rcon);
rc.commands().clone()
});
match commands {
Ok(commands) => self.send_msg_err(ClientGeneral::Control(commands))?,
Err(e) => {
error!(?e, "couldn't create RemoteController for own entity");
},
};
}
// 2) Build up a list of events for this frame, to be passed to the frontend.
@ -2187,6 +2178,17 @@ impl Client {
) -> Result<(), Error> {
prof_span!("handle_server_in_game_msg");
match msg {
ServerGeneral::AckControl(acked_ids) => {
if let Some(remote_controller) = self
.state
.ecs()
.write_storage::<RemoteController>()
.get_mut(self.entity())
{
remote_controller.acked(acked_ids);
remote_controller.maintain();
}
},
ServerGeneral::GroupUpdate(change_notification) => {
use comp::group::ChangeNotification::*;
// Note: we use a hashmap since this would not work with entities outside
@ -2868,8 +2870,12 @@ mod tests {
let mut clock = Clock::new(Duration::from_secs_f64(SPT));
//tick
let events_result: Result<Vec<Event>, Error> =
client.tick(ControllerInputs::default(), clock.dt(), |_| {});
let events_result: Result<Vec<Event>, Error> = client.tick(
comp::ControllerInputs::default(),
clock.dt(),
clock.total_tick_time(),
|_| {},
);
//chat functionality
client.send_chat("foobar".to_string());

View File

@ -58,9 +58,7 @@ pub enum ClientGeneral {
Character(CharacterId, ViewDistances),
Spectate(ViewDistances),
//Only in game
ControllerInputs(Box<comp::ControllerInputs>),
ControlEvent(comp::ControlEvent),
ControlAction(comp::ControlAction),
Control(comp::ControlCommands),
SetViewDistance(ViewDistances),
BreakBlock(Vec3<i32>),
PlaceBlock(Vec3<i32>, Block),
@ -118,9 +116,7 @@ impl ClientMsg {
c_type == ClientType::Game && presence.is_none()
},
//Only in game
ClientGeneral::ControllerInputs(_)
| ClientGeneral::ControlEvent(_)
| ClientGeneral::ControlAction(_)
ClientGeneral::Control(_)
| ClientGeneral::SetViewDistance(_)
| ClientGeneral::BreakBlock(_)
| ClientGeneral::PlaceBlock(_, _)

View File

@ -18,7 +18,7 @@ use common::{
uuid::Uuid,
weather::WeatherGrid,
};
use hashbrown::HashMap;
use hashbrown::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use tracing::warn;
@ -142,6 +142,7 @@ pub enum ServerGeneral {
CharacterSuccess,
SpectatorSuccess(Vec3<f32>),
//Ingame related
AckControl(HashSet<u64>),
GroupUpdate(comp::group::ChangeNotification<Uid>),
/// Indicate to the client that they are invited to join a group
Invite {
@ -315,6 +316,7 @@ impl ServerMsg {
},
//Ingame related
ServerGeneral::GroupUpdate(_)
| ServerGeneral::AckControl(_)
| ServerGeneral::Invite { .. }
| ServerGeneral::InvitePending(_)
| ServerGeneral::InviteComplete { .. }

View File

@ -103,6 +103,8 @@ impl Clock {
}
}
pub fn total_tick_time(&self) -> Duration { self.total_tick_time }
/// Do not modify without asking @xMAC94x first!
pub fn tick(&mut self) {
span!(_guard, "tick", "Clock::tick");

View File

@ -127,6 +127,7 @@ pub enum UtteranceKind {
* sounds */
}
/// instantaneous Events that can happen anytime and are
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ControlEvent {
//ToggleLantern,
@ -150,6 +151,8 @@ pub enum ControlEvent {
},
}
/// in contrast to events, action move a entity between 2 states, we want to
/// limit when they can happen
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum ControlAction {
SwapEquippedWeapons,

View File

@ -40,6 +40,8 @@ pub mod loot_owner;
#[cfg(not(target_arch = "wasm32"))]
pub mod projectile;
#[cfg(not(target_arch = "wasm32"))]
mod remote_controller;
#[cfg(not(target_arch = "wasm32"))]
pub mod shockwave;
#[cfg(not(target_arch = "wasm32"))]
pub mod skillset;
@ -108,6 +110,7 @@ pub use self::{
player::{AliasError, Player, MAX_ALIAS_LEN},
poise::{Poise, PoiseChange, PoiseState},
projectile::{Projectile, ProjectileConstructor},
remote_controller::{CommandGenerator, ControlCommands, RemoteController},
shockwave::{Shockwave, ShockwaveHitEntities},
skillset::{
skills::{self, Skill},

View File

@ -0,0 +1,240 @@
use crate::comp::Controller;
use hashbrown::HashSet;
use serde::{Deserialize, Serialize};
use specs::{Component, DenseVecStorage};
use std::{collections::VecDeque, time::Duration};
pub type ControlCommands = VecDeque<ControlCommand>;
/// Controller messages are real-time-combat relevant.
/// Controller contains all kind of inputs a entity can change
/// Remote controlled entities (e.g. clients) have a latency
/// They can predict the future locally and send us their TIMED controls
/// Other things, like chunk requests or build a weapon are done one other
/// channel via other methods.
#[derive(Debug)]
pub struct RemoteController {
///sorted, deduplicated
commands: ControlCommands,
existing_commands: HashSet<u64>,
max_hold: Duration,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ControlCommand {
id: u64,
source_time: Duration,
msg: Controller,
}
impl RemoteController {
/// delte old commands
pub fn maintain(&mut self) {
let min_allowed_age = self.commands.back().unwrap().source_time;
while let Some(first) = self.commands.front() {
if first.source_time + self.max_hold < min_allowed_age {
self.commands
.pop_front()
.map(|c| self.existing_commands.remove(&c.id));
} else {
break;
}
}
}
pub fn push(&mut self, command: ControlCommand) -> Option<u64> {
let id = command.id;
//check if command fits on the end.
if self.existing_commands.contains(&id) {
return None; // element already existed
}
self.existing_commands.insert(id);
self.commands.push_back(command);
Some(id)
}
pub fn append(&mut self, commands: ControlCommands) -> HashSet<u64> {
let mut result = HashSet::new();
for command in commands {
let id = command.id;
if !self.existing_commands.contains(&id) {
result.insert(id);
self.existing_commands.insert(id);
self.commands.push_back(command);
}
}
result
}
pub fn current(&self, time: Duration) -> Option<&ControlCommand> {
//TODO: actually sort it!
let mut lowest = None;
let mut lowest_cmd = None;
for c in &self.commands {
if c.source_time >= time {
let diff = c.source_time - time;
if match lowest {
None => true,
Some(lowest) => diff < lowest,
} {
lowest = Some(diff);
lowest_cmd = Some(c);
}
}
}
lowest_cmd
}
/// arrived at remote and no longer need to be hold locally
pub fn acked(&mut self, ids: HashSet<u64>) { self.commands.retain(|c| !ids.contains(&c.id)); }
pub fn commands(&self) -> &ControlCommands { &self.commands }
}
impl Default for RemoteController {
fn default() -> Self {
Self {
commands: VecDeque::new(),
existing_commands: HashSet::new(),
max_hold: Duration::from_secs(1),
}
}
}
impl Component for RemoteController {
type Storage = DenseVecStorage<Self>;
}
#[derive(Default)]
pub struct CommandGenerator {
id: u64,
}
impl CommandGenerator {
pub fn gen(&mut self, time: Duration, msg: Controller) -> ControlCommand {
self.id += 1;
ControlCommand {
source_time: time,
id: self.id,
msg,
}
}
}
impl ControlCommand {
pub fn msg(&self) -> &Controller { &self.msg }
}
#[cfg(test)]
mod tests {
use super::*;
fn generate_control_cmds(count: usize) -> ControlCommands {
let mut result = VecDeque::new();
let mut generator = CommandGenerator::default();
let mut time = Duration::new(0, 0);
const INCREASE: Duration = Duration::from_millis(33);
for _ in 0..count {
let msg = Controller::default();
let cc = generator.gen(time, msg);
result.push_back(cc);
time += INCREASE;
}
result
}
#[test]
fn test_resend_data() {
let data = generate_control_cmds(5);
let mut list = RemoteController::default();
assert_eq!(list.push(data[0].clone()), Some(1));
assert_eq!(list.commands.len(), 1);
assert_eq!(list.push(data[0].clone()), None);
assert_eq!(list.push(data[1].clone()), Some(2));
assert_eq!(list.commands.len(), 2);
assert_eq!(list.push(data[0].clone()), None);
assert_eq!(list.push(data[1].clone()), None);
assert_eq!(list.push(data[2].clone()), Some(3));
assert_eq!(list.commands.len(), 3);
assert_eq!(list.push(data[1].clone()), None);
assert_eq!(list.push(data[2].clone()), None);
assert_eq!(list.push(data[3].clone()), Some(4));
assert_eq!(list.commands.len(), 4);
assert_eq!(list.push(data[2].clone()), None);
assert_eq!(list.push(data[3].clone()), None);
assert_eq!(list.push(data[4].clone()), Some(5));
assert_eq!(list.commands.len(), 5);
}
#[test]
fn test_auto_evict() {
let data = generate_control_cmds(6);
let mut list = RemoteController::default();
list.max_hold = Duration::from_millis(100);
assert_eq!(list.push(data[0].clone()), Some(1));
assert_eq!(list.push(data[1].clone()), Some(2));
assert_eq!(list.push(data[2].clone()), Some(3));
assert_eq!(list.commands.len(), 3);
assert_eq!(list.push(data[3].clone()), Some(4));
assert_eq!(list.commands.len(), 4);
assert_eq!(list.commands[0].id, 1);
assert_eq!(list.push(data[4].clone()), Some(5));
assert_eq!(list.commands.len(), 5);
list.maintain();
assert_eq!(list.commands.len(), 4);
assert_eq!(list.commands[0].id, 2);
assert_eq!(list.push(data[5].clone()), Some(6));
assert_eq!(list.commands.len(), 5);
list.maintain();
assert_eq!(list.commands.len(), 4);
assert_eq!(list.commands[0].id, 3);
assert_eq!(list.commands[1].id, 4);
assert_eq!(list.commands[2].id, 5);
assert_eq!(list.commands[3].id, 6);
}
#[test]
fn test_acked() {
let data = generate_control_cmds(7);
let mut list = RemoteController::default();
assert_eq!(list.push(data[0].clone()), Some(1));
assert_eq!(list.push(data[1].clone()), Some(2));
assert_eq!(list.push(data[2].clone()), Some(3));
assert_eq!(list.push(data[3].clone()), Some(4));
assert_eq!(list.push(data[4].clone()), Some(5));
let mut to_export = list.commands().iter().map(|e| e.id).collect::<HashSet<_>>();
// damange one entry
to_export.remove(&3);
list.acked(to_export);
assert_eq!(list.push(data[5].clone()), Some(6));
assert_eq!(list.push(data[6].clone()), Some(7));
println!("asd{:?}", &list);
let to_export = list.commands().clone();
assert_eq!(to_export.len(), 3);
assert_eq!(to_export[0].id, 3);
assert_eq!(to_export[1].id, 6);
assert_eq!(to_export[2].id, 7);
}
#[test]
fn test_append() {
let data = generate_control_cmds(5);
let mut list = RemoteController::default();
let set = list.append(data);
assert_eq!(set.len(), 5);
assert!(!set.contains(&0));
assert!(set.contains(&1));
assert!(set.contains(&2));
assert!(set.contains(&3));
assert!(set.contains(&4));
assert!(set.contains(&5));
assert!(!set.contains(&6));
}
}

View File

@ -207,7 +207,7 @@ impl State {
ecs.register::<comp::Admin>();
// Register components send from clients -> server
ecs.register::<comp::Controller>();
ecs.register::<comp::RemoteController>();
// Register components send directly from server -> all but one client
ecs.register::<comp::PhysicsState>();
@ -224,6 +224,7 @@ impl State {
// Register client-local components
// TODO: only register on the client
ecs.register::<comp::Controller>();
ecs.register::<comp::LightAnimation>();
ecs.register::<sync_interp::InterpBuffer<comp::Pos>>();
ecs.register::<sync_interp::InterpBuffer<comp::Vel>>();

View File

@ -10,6 +10,7 @@ mod interpolation;
pub mod melee;
mod mount;
pub mod phys;
pub mod predict_controller;
pub mod projectile;
mod shockwave;
mod stats;
@ -19,6 +20,7 @@ use common_ecs::{dispatch, System};
use specs::DispatcherBuilder;
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch::<predict_controller::Sys>(dispatch_builder, &[]);
//TODO: don't run interpolation on server
dispatch::<interpolation::Sys>(dispatch_builder, &[]);
dispatch::<mount::Sys>(dispatch_builder, &[]);

View File

@ -0,0 +1,47 @@
use common::comp::{Controller, RemoteController};
use common_ecs::{Job, Origin, Phase, System};
use specs::{shred::ResourceId, Entities, Join, ReadStorage, SystemData, World, WriteStorage};
use std::time::Duration;
#[derive(SystemData)]
pub struct ReadData<'a> {
entities: Entities<'a>,
remote_controllers: ReadStorage<'a, RemoteController>,
}
#[derive(Default)]
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (ReadData<'a>, WriteStorage<'a, Controller>);
const NAME: &'static str = "predict_controller";
const ORIGIN: Origin = Origin::Common;
const PHASE: Phase = Phase::Create;
fn run(_job: &mut Job<Self>, (read_data, mut controllers): Self::SystemData) {
for (entity, _a) in (&read_data.entities, &read_data.remote_controllers).join() {
let i = _a.commands().len();
tracing::warn!(?i, "foo");
let _ = controllers
.entry(entity)
.map(|e| e.or_insert_with(Default::default));
}
for (_, remote_controller, controller) in (
&read_data.entities,
&read_data.remote_controllers,
&mut controllers,
)
.join()
{
// grab the most fitting entry
let time = Duration::from_millis(1000);
let r = remote_controller.current(time).map(|c| c.msg());
// Do nothing when already populated and we dont have a new value
if let Some(r) = r {
*controller = r.clone()
}
}
}
}

View File

@ -103,6 +103,7 @@ impl Client {
},
//In-game related
ServerGeneral::GroupUpdate(_)
| ServerGeneral::AckControl(_)
| ServerGeneral::Invite { .. }
| ServerGeneral::InvitePending(_)
| ServerGeneral::InviteComplete { .. }
@ -175,6 +176,7 @@ impl Client {
},
//In-game related
ServerGeneral::GroupUpdate(_)
| ServerGeneral::AckControl(_)
| ServerGeneral::Invite { .. }
| ServerGeneral::InvitePending(_)
| ServerGeneral::InviteComplete { .. }

View File

@ -3,8 +3,8 @@ use crate::TerrainPersistence;
use crate::{client::Client, presence::Presence, Settings};
use common::{
comp::{
Admin, AdminRole, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player,
Pos, SkillSet, Vel,
Admin, AdminRole, CanBuild, ForceUpdate, Health, Ori, Player, Pos, RemoteController,
SkillSet, Vel,
},
event::{EventBus, ServerEvent},
link::Is,
@ -59,7 +59,7 @@ impl Sys {
position: Option<&mut Pos>,
velocity: Option<&mut Vel>,
orientation: Option<&mut Ori>,
controller: Option<&mut Controller>,
remote_controller: &mut RemoteController,
settings: &Read<'_, Settings>,
build_areas: &Read<'_, BuildAreas>,
player_physics_setting: Option<&mut PlayerPhysicsSetting>,
@ -93,32 +93,32 @@ impl Sys {
client.send(ServerGeneral::SetViewDistance(clamped_vds.terrain))?;
}
},
ClientGeneral::ControllerInputs(inputs) => {
ClientGeneral::Control(rc) => {
if presence.kind.controlling_char() {
if let Some(controller) = controller {
controller.inputs.update_with_new(*inputs);
}
}
},
ClientGeneral::ControlEvent(event) => {
if presence.kind.controlling_char() {
// Skip respawn if client entity is alive
if let ControlEvent::Respawn = event {
if healths.get(entity).map_or(true, |h| !h.is_dead) {
//Todo: comment why return!
return Ok(());
let ids = remote_controller.append(rc);
remote_controller.maintain();
// confirm controls
client.send(ServerGeneral::AckControl(ids))?;
//Todo: FIXME!!!
/*
// Skip respawn if client entity is alive
if let ControlEvent::Respawn = event {
if healths.get(entity).map_or(true, |h| !h.is_dead) {
//Todo: comment why return!
return Ok(());
}
}
}
if let Some(controller) = controllers.get_mut(entity) {
controller.push_event(event);
}
}
}
if let Some(controller) = controller {
controller.push_event(event);
}
}
},
ClientGeneral::ControlAction(event) => {
if presence.kind.controlling_char() {
if let Some(controller) = controller {
controller.push_action(event);
}
},
ClientGeneral::ControlAction(event) => {
if presence.kind.controlling_char() {
if let Some(controller) = controllers.get_mut(entity) {
controller.push_action(event);
*/
}
},
ClientGeneral::PlayerPhysics { pos, vel, ori, force_counter } => {
@ -330,7 +330,7 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Ori>,
WriteStorage<'a, Presence>,
WriteStorage<'a, Client>,
WriteStorage<'a, Controller>,
WriteStorage<'a, RemoteController>,
Read<'a, Settings>,
Read<'a, BuildAreas>,
Write<'a, PlayerPhysicsSettings>,
@ -361,7 +361,7 @@ impl<'a> System<'a> for Sys {
mut orientations,
mut presences,
mut clients,
mut controllers,
mut remote_controllers,
settings,
build_areas,
mut player_physics_settings_,
@ -390,7 +390,7 @@ impl<'a> System<'a> for Sys {
(&mut positions).maybe(),
(&mut velocities).maybe(),
(&mut orientations).maybe(),
(&mut controllers).maybe(),
(&mut remote_controllers).maybe(),
)
.join()
// NOTE: Required because Specs has very poor work splitting for sparse joins.
@ -407,7 +407,7 @@ impl<'a> System<'a> for Sys {
ref mut pos,
ref mut vel,
ref mut ori,
ref mut controller,
remote_controller,
)| {
let old_player_physics_setting = maybe_player.map(|p| {
player_physics_settings
@ -421,6 +421,14 @@ impl<'a> System<'a> for Sys {
// ingame messages to be ignored.
let mut clearable_maybe_presence = maybe_presence.as_deref_mut();
let mut skill_set = skill_set.map(Cow::Borrowed);
let mut new_remote_controller = None;
let remote_controller = match remote_controller {
Some(rc) => rc,
None => {
new_remote_controller = Some(RemoteController::default());
new_remote_controller.as_mut().unwrap()
}
};
let _ = super::try_recv_all(client, 2, |client, msg| {
Self::handle_client_in_game_msg(
server_emitter,
@ -437,7 +445,7 @@ impl<'a> System<'a> for Sys {
pos.as_deref_mut(),
vel.as_deref_mut(),
ori.as_deref_mut(),
controller.as_deref_mut(),
remote_controller,
&settings,
&build_areas,
new_player_physics_setting.as_mut(),
@ -467,27 +475,26 @@ impl<'a> System<'a> for Sys {
let physics_update = maybe_player.map(|p| p.uuid())
.zip(new_player_physics_setting
.filter(|_| old_player_physics_setting != new_player_physics_setting));
(skill_set_update, physics_update)
(skill_set_update, physics_update, new_remote_controller.map(|c| (entity, c)))
},
)
// NOTE: Would be nice to combine this with the map_init somehow, but I'm not sure if
// that's possible.
.filter(|(x, y)| x.is_some() || y.is_some())
.filter(|(x, y, z)| x.is_some() || y.is_some() || z.is_some())
// NOTE: I feel like we shouldn't actually need to allocate here, but hopefully this
// doesn't turn out to be important as there shouldn't be that many connected clients.
// The reason we can't just use unzip is that the two sides might be different lengths.
.collect::<Vec<_>>();
let player_physics_settings = &mut *player_physics_settings_;
// Deferred updates to skillsets and player physics.
// Deferred updates to skillsets and player physics and new_remote_controller.
//
// NOTE: It is an invariant that there is at most one client entry per player
// uuid; since we joined on clients, it follows that there's just one update
// per uuid, so the physics update is sound and doesn't depend on evaluation
// order, even though we're not updating directly by entity or uid (note that
// for a given entity, we process messages serially).
deferred_updates
.iter_mut()
.for_each(|(skill_set_update, physics_update)| {
deferred_updates.iter_mut().for_each(
|(skill_set_update, physics_update, new_remote_controller)| {
if let Some((entity, new_skill_set)) = skill_set_update {
// We know this exists, because we already iterated over it with the skillset
// lock taken, so we can ignore the error.
@ -506,7 +513,11 @@ impl<'a> System<'a> for Sys {
.settings
.insert(uuid, player_physics_setting);
}
});
if let Some((uuid, new_remote_controller)) = new_remote_controller.take() {
let _ = remote_controllers.insert(uuid, new_remote_controller);
}
},
);
// Finally, drop the deferred updates in another thread.
slow_jobs.spawn("CHUNK_DROP", move || {
drop(deferred_updates);

View File

@ -208,6 +208,7 @@ impl PlayState for CharSelectionState {
let res = self.client.borrow_mut().tick(
comp::ControllerInputs::default(),
global_state.clock.dt(),
global_state.clock.total_tick_time(),
|_| {},
);
match res {

View File

@ -226,6 +226,7 @@ impl PlayState for MainMenuState {
match client.tick(
comp::ControllerInputs::default(),
global_state.clock.dt(),
global_state.clock.total_tick_time(),
|_| {},
) {
Ok(events) => {

View File

@ -270,7 +270,12 @@ impl SessionState {
self.mumble_link.update(player_pos, player_pos);
}
for event in client.tick(self.inputs.clone(), dt, crate::ecs::sys::add_local_systems)? {
for event in client.tick(
self.inputs.clone(),
dt,
global_state.clock.total_tick_time(),
crate::ecs::sys::add_local_systems,
)? {
match event {
client::Event::Chat(m) => {
self.hud.new_message(m);