Skip to content

Commit

Permalink
Replace NodeId implementation with global hash
Browse files Browse the repository at this point in the history
  • Loading branch information
patowen committed Sep 11, 2023
1 parent 1f5780b commit 99b1498
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 139 deletions.
20 changes: 12 additions & 8 deletions client/src/sim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use common::{
graph::{Graph, NodeId},
node::{populate_fresh_nodes, DualGraph},
proto::{self, Character, CharacterInput, CharacterState, Command, Component, Position},
sanitize_motion_input, EntityId, GraphEntities, SimConfig, Step,
sanitize_motion_input,
traversal::ensure_nearby,
EntityId, GraphEntities, SimConfig, Step,
};

/// Game state
Expand Down Expand Up @@ -96,6 +98,15 @@ impl Sim {
self.handle_net(msg);
}

if let Some(params) = self.params.as_ref() {
ensure_nearby(
&mut self.graph,
self.prediction.predicted_position(),
f64::from(params.cfg.view_distance),
);
populate_fresh_nodes(&mut self.graph);
}

if let Some(step_interval) = self.params.as_ref().map(|x| x.cfg.step_interval) {
self.since_input_sent += dt;
if let Some(overflow) = self.since_input_sent.checked_sub(step_interval) {
Expand Down Expand Up @@ -243,13 +254,6 @@ impl Sim {
None => error!(%id, "despawned unknown entity"),
}
}
if !msg.nodes.is_empty() {
trace!(count = msg.nodes.len(), "adding nodes");
}
for node in &msg.nodes {
self.graph.insert_child(node.parent, node.side);
}
populate_fresh_nodes(&mut self.graph);
}

fn spawn(
Expand Down
138 changes: 35 additions & 103 deletions common/src/graph.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#![allow(clippy::len_without_is_empty)]

use std::convert::TryFrom;
use std::fmt;
use std::num::NonZeroU32;

use blake3::Hasher;
use fxhash::FxHashMap;
use serde::{Deserialize, Serialize};

use crate::{
Expand All @@ -16,16 +13,18 @@ use crate::{
/// Graph of the right dodecahedral tiling of H^3
#[derive(Debug, Clone)]
pub struct Graph<N> {
nodes: Vec<Node<N>>,
nodes: FxHashMap<NodeId, Node<N>>,
/// This field stores implicitly added nodes to ensure that they're initialized in the correct
/// order
fresh: Vec<NodeId>,
}

impl<N> Graph<N> {
pub fn new() -> Self {
let mut nodes = FxHashMap::default();
nodes.insert(NodeId::ROOT, Node::new(None, 0));
Self {
nodes: vec![Node::new(None, 0, 0)],
nodes,
fresh: vec![NodeId::ROOT],
}
}
Expand All @@ -37,7 +36,7 @@ impl<N> Graph<N> {

#[inline]
pub fn contains(&self, node: NodeId) -> bool {
node.0.get() as usize <= self.nodes.len()
self.nodes.contains_key(&node)
}

/// Nodes created since the last call to `clear_fresh`
Expand Down Expand Up @@ -93,22 +92,22 @@ impl<N> Graph<N> {

#[inline]
pub fn get(&self, node: NodeId) -> &Option<N> {
&self.nodes[node.idx()].value
&self.nodes[&node].value
}

#[inline]
pub fn get_mut(&mut self, node: NodeId) -> &mut Option<N> {
&mut self.nodes[node.idx()].value
&mut self.nodes.get_mut(&node).unwrap().value
}

#[inline]
pub fn neighbor(&self, node: NodeId, which: Side) -> Option<NodeId> {
self.nodes[node.idx()].neighbors[which as usize]
self.nodes[&node].neighbors[which as usize]
}

#[inline]
pub fn length(&self, node: NodeId) -> u32 {
self.nodes[node.idx()].length
self.nodes[&node].length
}

/// Given a `transform` relative to a `reference` node, computes the node
Expand Down Expand Up @@ -141,21 +140,13 @@ impl<N> Graph<N> {

#[inline]
pub fn parent(&self, node: NodeId) -> Option<Side> {
self.nodes[node.idx()].parent_side
}

/// Iterate over every node and its parent
pub fn tree(&self) -> TreeIter<'_, N> {
TreeIter {
id: NodeId::from_idx(1),
remaining: &self.nodes[1..],
}
self.nodes[&node].parent_side
}

/// Ensures that the neighbour node at a particular side of a particular node exists in the graph,
/// as well as the nodes from the origin to the neighbour node.
pub fn ensure_neighbor(&mut self, node: NodeId, side: Side) -> NodeId {
let v = &self.nodes[node.idx()];
let v = &self.nodes[&node];
if let Some(x) = v.neighbors[side as usize] {
// Neighbor already exists
return x;
Expand All @@ -173,7 +164,7 @@ impl<N> Graph<N> {
}

// Neighbor is closer to the origin; find it, backfilling if necessary
let x = self.nodes[v.parent().unwrap().idx()].neighbors[side as usize].unwrap();
let x = self.nodes[&v.parent().unwrap()].neighbors[side as usize].unwrap();
let parent_side = v.parent_side.unwrap();
let neighbor = self.ensure_neighbor(x, parent_side);
self.link_neighbors(node, neighbor, side);
Expand All @@ -182,8 +173,8 @@ impl<N> Graph<N> {

/// Whether `node`'s neighbor along `side` is closer than it to the origin
fn is_near_side(&self, node: NodeId, side: Side) -> bool {
let v = &self.nodes[node.idx()];
v.neighbors[side as usize].map_or(false, |x| self.nodes[x.idx()].length < v.length)
let v = &self.nodes[&node];
v.neighbors[side as usize].map_or(false, |x| self.nodes[&x].length < v.length)
}

pub fn insert_child(&mut self, parent: NodeId, side: Side) -> NodeId {
Expand All @@ -199,16 +190,15 @@ impl<N> Graph<N> {
.min_by_key(|&(side, _)| side)
.unwrap();
let mut hasher = Hasher::new();
hasher.update(&self.nodes[predecessor.idx()].hash.to_le_bytes());
hasher.update(&predecessor.0.to_le_bytes());
hasher.update(&[path_side as u8]);
let mut xof = hasher.finalize_xof();
let mut hash = [0; 16];
xof.fill(&mut hash);
let hash = u128::from_le_bytes(hash);
let id = NodeId(u128::from_le_bytes(hash));

let id = NodeId::from_idx(self.nodes.len());
let length = self.nodes[parent.idx()].length + 1;
self.nodes.push(Node::new(Some(side), length, hash));
let length = self.nodes[&parent].length + 1;
self.nodes.insert(id, Node::new(Some(side), length));
self.link_neighbors(id, parent, side);
for (side, neighbor) in shorter_neighbors {
self.link_neighbors(id, neighbor, side);
Expand All @@ -217,11 +207,6 @@ impl<N> Graph<N> {
id
}

#[inline]
pub fn hash_of(&self, node: NodeId) -> u128 {
self.nodes[node.idx()].hash
}

/// Ensure all shorter neighbors of a not-yet-created child node exist and return them, excluding the given parent node
fn populate_shorter_neighbors_of_child(
&mut self,
Expand All @@ -237,7 +222,7 @@ impl<N> Graph<N> {
{
continue;
}
let x = self.nodes[parent.idx()].neighbors[neighbor_side as usize].unwrap();
let x = self.nodes[&parent].neighbors[neighbor_side as usize].unwrap();
let neighbor = self.ensure_neighbor(x, parent_side);
neighbors[count] = Some((neighbor_side, neighbor));
count += 1;
Expand All @@ -248,11 +233,11 @@ impl<N> Graph<N> {
/// Register `a` and `b` as adjacent along `side`
fn link_neighbors(&mut self, a: NodeId, b: NodeId, side: Side) {
debug_assert!(
self.nodes[a.idx()].neighbors[side as usize].is_none()
&& self.nodes[b.idx()].neighbors[side as usize].is_none()
self.nodes[&a].neighbors[side as usize].is_none()
&& self.nodes[&b].neighbors[side as usize].is_none()
);
self.nodes[a.idx()].neighbors[side as usize] = Some(b);
self.nodes[b.idx()].neighbors[side as usize] = Some(a);
self.nodes.get_mut(&a).unwrap().neighbors[side as usize] = Some(b);
self.nodes.get_mut(&b).unwrap().neighbors[side as usize] = Some(a);
}
}

Expand All @@ -262,30 +247,14 @@ impl<N> Default for Graph<N> {
}
}

#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct NodeId(NonZeroU32);
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct NodeId(u128);

impl NodeId {
pub const ROOT: Self = Self(unsafe { NonZeroU32::new_unchecked(1) });

fn from_idx(x: usize) -> Self {
Self(NonZeroU32::new(u32::try_from(x + 1).expect("graph grew too large")).unwrap())
}

fn idx(self) -> usize {
(self.0.get() - 1) as usize
}
}

impl From<NodeId> for u32 {
fn from(x: NodeId) -> u32 {
x.0.get() - 1
}
}
pub const ROOT: Self = Self(0);

impl fmt::Debug for NodeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(self.0.get() - 1).fmt(f)
pub fn hash(&self) -> u128 {
self.0
}
}

Expand All @@ -296,17 +265,15 @@ struct Node<N> {
/// Distance to origin via parents
length: u32,
neighbors: [Option<NodeId>; SIDE_COUNT],
hash: u128,
}

impl<N> Node<N> {
fn new(parent_side: Option<Side>, length: u32, hash: u128) -> Self {
fn new(parent_side: Option<Side>, length: u32) -> Self {
Self {
value: None,
parent_side,
length,
neighbors: [None; SIDE_COUNT],
hash,
}
}

Expand All @@ -315,27 +282,9 @@ impl<N> Node<N> {
}
}

pub struct TreeIter<'a, N> {
id: NodeId,
remaining: &'a [Node<N>],
}

impl<N> Iterator for TreeIter<'_, N> {
type Item = (Side, NodeId);

fn next(&mut self) -> Option<Self::Item> {
let (node, rest) = self.remaining.split_first()?;
self.remaining = rest;
self.id = NodeId::from_idx(self.id.idx() + 1);
let side = node.parent_side.unwrap();
Some((side, node.neighbors[side as usize].unwrap()))
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::{proto::Position, traversal::ensure_nearby};
use approx::*;

#[test]
Expand All @@ -348,14 +297,14 @@ mod tests {
assert_eq!(graph.len(), 2);
assert_eq!(a, a2);
assert_eq!(graph.ensure_neighbor(a, Side::A), NodeId::ROOT);
assert_eq!(graph.nodes[a.idx()].length, 1);
assert_eq!(graph.nodes[&a].length, 1);
let b = graph.ensure_neighbor(NodeId::ROOT, Side::B);
assert_eq!(graph.len(), 3);
assert_eq!(graph.ensure_neighbor(b, Side::B), NodeId::ROOT);
let c = graph.ensure_neighbor(a, Side::C);
assert!(graph.len() > 4);
assert_eq!(graph.ensure_neighbor(c, Side::C), a);
assert_eq!(graph.nodes[c.idx()].length, 2);
assert_eq!(graph.nodes[&c].length, 2);
}

#[test]
Expand All @@ -382,7 +331,7 @@ mod tests {
);
assert!(common.contains(&NodeId::ROOT));
let other = common.iter().cloned().find(|&x| x != NodeId::ROOT).unwrap();
assert_eq!(graph.nodes[other.idx()].length, 2);
assert_eq!(graph.nodes[&other].length, 2);
}

#[test]
Expand All @@ -402,38 +351,21 @@ mod tests {
}
}

#[test]
fn rebuild_from_tree() {
let mut a = Graph::<()>::default();
ensure_nearby(&mut a, &Position::origin(), 3.0);
let mut b = Graph::<()>::default();
for (side, parent) in a.tree() {
b.insert_child(parent, side);
}
assert_eq!(a.len(), b.len());
for (c, d) in a.tree().zip(b.tree()) {
assert_eq!(c.0, d.0);
assert_eq!(a.neighbor(c.1, c.0), b.neighbor(c.1, c.0));
}
}

#[test]
fn hash_consistency() {
let h1 = {
let mut g = Graph::<()>::new();
let n1 = g.ensure_neighbor(NodeId::ROOT, Side::A);
let n2 = g.ensure_neighbor(n1, Side::B);
let n3 = g.ensure_neighbor(n2, Side::C);
let n4 = g.ensure_neighbor(n3, Side::D);
g.hash_of(n4)
g.ensure_neighbor(n3, Side::D)
};
let h2 = {
let mut g = Graph::<()>::new();
let n1 = g.ensure_neighbor(NodeId::ROOT, Side::C);
let n2 = g.ensure_neighbor(n1, Side::A);
let n3 = g.ensure_neighbor(n2, Side::B);
let n4 = g.ensure_neighbor(n3, Side::D);
g.hash_of(n4)
g.ensure_neighbor(n3, Side::D)
};

assert_eq!(h1, h2);
Expand Down
1 change: 0 additions & 1 deletion common/src/proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ pub struct Spawns {
pub step: Step,
pub spawns: Vec<(EntityId, Vec<Component>)>,
pub despawns: Vec<EntityId>,
pub nodes: Vec<FreshNode>,
}

#[derive(Debug, Serialize, Deserialize)]
Expand Down
4 changes: 2 additions & 2 deletions common/src/worldgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ impl NodeState {
let parent_side = graph.parent(node).unwrap();
let parent_node = graph.neighbor(node, parent_side).unwrap();
let parent_state = &graph.get(parent_node).as_ref().unwrap().state;
let spice = graph.hash_of(node) as u64;
let spice = node.hash() as u64;
EnviroFactors::varied_from(parent_state.enviro, spice)
}
(Some((a_side, a_state)), Some((b_side, b_state))) => {
Expand Down Expand Up @@ -188,7 +188,7 @@ impl ChunkParams {
&& ((state.road_state == East) || (state.road_state == West)),
is_road_support: ((state.kind == Land) || (state.kind == DeepLand))
&& ((state.road_state == East) || (state.road_state == West)),
node_spice: graph.hash_of(chunk.node) as u64,
node_spice: chunk.node.hash() as u64,
})
}

Expand Down
5 changes: 1 addition & 4 deletions server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,7 @@ impl Server {
let mut delta = delta.clone();
delta.latest_input = client.latest_input_processed;
let r1 = handles.unordered.try_send(delta);
let r2 = if !spawns.spawns.is_empty()
|| !spawns.despawns.is_empty()
|| !spawns.nodes.is_empty()
{
let r2 = if !spawns.spawns.is_empty() || !spawns.despawns.is_empty() {
handles.ordered.try_send(spawns.clone())
} else {
Ok(())
Expand Down
Loading

0 comments on commit 99b1498

Please sign in to comment.