Files
magicka-vm/crates/world_model/src/primitives.rs
T

215 lines
5.4 KiB
Rust

//! Deterministic primitives shared across the whole framework: stable ids,
//! a stable content hash, and a deterministic RNG. Everything here is
//! reproducible from a seed so that replay is bit-exact.
use std::fmt;
/// Number of independent world domains. The spec mandates >= 8.
pub const NUM_DOMAINS: usize = 8;
/// Observed value lanes per domain.
pub const LANES: usize = 4;
/// Hidden (unobserved) value lanes per domain. These create the
/// hidden/observed state divergence the spec requires.
pub const HIDDEN_LANES: usize = 2;
macro_rules! id_type {
($name:ident, $inner:ty) => {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name(pub $inner);
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({})", stringify!($name), self.0)
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
};
}
id_type!(WorldId, u64);
id_type!(ProgramId, u64);
id_type!(ContractId, u64);
/// Identifies one of the [`NUM_DOMAINS`] domains.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DomainId(pub u8);
impl fmt::Debug for DomainId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DomainId({})", self.0)
}
}
/// A stable 64-bit content hash. Used for canonical comparison, replay
/// hashes, and behavior fingerprints. Implemented with FNV-1a so the value
/// is identical across machines and runs.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Hash(pub u64);
impl fmt::Debug for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Hash({:016x})", self.0)
}
}
impl fmt::Display for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:016x}", self.0)
}
}
const FNV_OFFSET: u64 = 0xcbf29ce484222325;
const FNV_PRIME: u64 = 0x100000001b3;
/// Streaming stable hasher (FNV-1a, 64 bit).
#[derive(Clone, Copy)]
pub struct Hasher {
state: u64,
}
impl Default for Hasher {
fn default() -> Self {
Hasher { state: FNV_OFFSET }
}
}
impl Hasher {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn write_u8(&mut self, b: u8) {
self.state ^= b as u64;
self.state = self.state.wrapping_mul(FNV_PRIME);
}
#[inline]
pub fn write_u64(&mut self, v: u64) {
for i in 0..8 {
self.write_u8(((v >> (i * 8)) & 0xff) as u8);
}
}
#[inline]
pub fn write_i64(&mut self, v: i64) {
self.write_u64(v as u64);
}
#[inline]
pub fn write_usize(&mut self, v: usize) {
self.write_u64(v as u64);
}
#[inline]
pub fn write_bytes(&mut self, bytes: &[u8]) {
for &b in bytes {
self.write_u8(b);
}
}
/// Mix in a label so structurally different streams that happen to share
/// numbers do not collide.
#[inline]
pub fn write_tag(&mut self, tag: &str) {
self.write_bytes(tag.as_bytes());
self.write_u8(0xff);
}
#[inline]
pub fn finish(&self) -> Hash {
Hash(self.state)
}
}
/// Hash a slice of i64 with a tag.
pub fn hash_i64_slice(tag: &str, vals: &[i64]) -> Hash {
let mut h = Hasher::new();
h.write_tag(tag);
h.write_usize(vals.len());
for &v in vals {
h.write_i64(v);
}
h.finish()
}
/// Combine several hashes into one (order sensitive).
pub fn combine_hashes(tag: &str, hashes: &[Hash]) -> Hash {
let mut h = Hasher::new();
h.write_tag(tag);
for hh in hashes {
h.write_u64(hh.0);
}
h.finish()
}
/// Deterministic SplitMix64 RNG. Fully reproducible from a seed.
#[derive(Clone, Copy, Debug)]
pub struct Rng {
state: u64,
}
impl Rng {
pub fn new(seed: u64) -> Self {
// Avoid the trivial all-zero state.
Rng {
state: seed ^ 0x9e3779b97f4a7c15,
}
}
/// Derive a sub-stream from a seed and a label, so independent concerns
/// never accidentally share a stream.
pub fn derive(seed: u64, tag: &str) -> Self {
let mut h = Hasher::new();
h.write_tag(tag);
h.write_u64(seed);
Rng::new(h.finish().0)
}
#[inline]
pub fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_add(0x9e3779b97f4a7c15);
let mut z = self.state;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
z ^ (z >> 31)
}
#[inline]
pub fn next_i64(&mut self) -> i64 {
self.next_u64() as i64
}
/// Uniform-ish integer in `[0, n)`.
#[inline]
pub fn below(&mut self, n: usize) -> usize {
if n == 0 {
return 0;
}
(self.next_u64() % (n as u64)) as usize
}
/// Integer in `[lo, hi]` inclusive.
#[inline]
pub fn range_i64(&mut self, lo: i64, hi: i64) -> i64 {
if hi <= lo {
return lo;
}
let span = (hi - lo) as u64 + 1;
lo + (self.next_u64() % span) as i64
}
#[inline]
pub fn chance(&mut self, p: f64) -> bool {
(self.next_u64() as f64 / u64::MAX as f64) < p
}
#[inline]
pub fn next_bool(&mut self) -> bool {
self.next_u64() & 1 == 1
}
}