use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::cell::RefCell;
use std::println;
use std::time::Instant;
use crate::ecs::asset_id::AssetId;
use crate::gfx::render_types::{DrawObject, MaterialUniforms, SkinnedDrawObject};
use crate::gfx::transform::IDENTITY;
const TARGET_NS: u128 = 200_000_000;
const MAX_ITERS: u64 = 1 << 20;
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Pace {
Timed,
Once,
}
pub(crate) fn bench<R>(pace: Pace, name: &str, items: u64, body: impl FnMut() -> R) {
bench_over(TARGET_NS, pace, name, items, body);
}
fn bench_over<R>(target_ns: u128, pace: Pace, name: &str, items: u64, mut body: impl FnMut() -> R) {
if pace == Pace::Once {
core::hint::black_box(body());
return;
}
let mut iters: u64 = 1;
loop {
let start = Instant::now();
for _ in 0..iters {
core::hint::black_box(body());
}
if start.elapsed().as_nanos() >= target_ns || iters >= MAX_ITERS {
break;
}
iters = iters.saturating_mul(4).min(MAX_ITERS);
}
let start = Instant::now();
for _ in 0..iters {
core::hint::black_box(body());
}
let elapsed = start.elapsed();
let before = crate::memory::alloc_count().unwrap_or(0);
for _ in 0..iters {
core::hint::black_box(body());
}
let allocated = crate::memory::alloc_count()
.unwrap_or(0)
.saturating_sub(before);
let units = (iters * items.max(1)) as f64;
let per_item_ns = elapsed.as_secs_f64() * 1e9 / units;
let allocs = allocated as f64 / units;
println!(" {name:<40} {per_item_ns:>10.2} ns/item {allocs:>10.3} allocs/item");
}
std::thread_local! {
static NAMES: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
}
fn resolve(name: &str) -> u32 {
NAMES.with(|names| {
let mut names = names.borrow_mut();
match names.iter().position(|n| n == name) {
Some(id) => id as u32,
None => {
names.push(name.to_string());
(names.len() - 1) as u32
}
}
})
}
pub(crate) fn reset_interner() {
crate::ecs::resolver::set_name_resolver(resolve);
NAMES.with(|names| names.borrow_mut().clear());
}
pub(crate) fn intern(name: &str) -> AssetId {
AssetId(resolve(name))
}
pub(crate) fn intern_all(names: &[&str]) {
for n in names {
resolve(n);
}
}
pub(crate) fn draw_object() -> DrawObject {
DrawObject {
vertex_offset: 0,
vertex_count: 8,
index_offset: 12,
index_count: 36,
base_vertex: 4,
geometry_generation: 0,
shader_bucket: 0,
model: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 2.0, 0.0, 0.0],
[0.0, 0.0, 3.0, 0.0],
[5.0, 6.0, 7.0, 1.0],
],
texture_slot: 9,
normal_map_slot: 2,
material: MaterialUniforms {
roughness: 0.3,
metallic: 0.7,
macro_variation: 0.5,
terrain_blend: 0.0,
tint: [0.1, 0.2, 0.3],
_pad2: 0.0,
emissive: [0.4, 0.5, 0.6],
secondary_blend_sharpness: 0.5,
albedo_secondary_index: 0,
normal_secondary_index: 0,
emissive_map_index: 0,
orm_map_index: 0,
alpha_cutoff: 0.0,
opacity: 1.0,
transparent: 0,
see_through: 0,
},
visible: true,
resident: true,
bb_min: [-1.0, -2.0, -3.0],
bb_max: [1.0, 2.0, 3.0],
cull_distance: 42.0,
lod_alternates: Vec::new(),
}
}
pub(crate) fn skinned_draw_object() -> SkinnedDrawObject {
SkinnedDrawObject {
vertex_base: 0,
vertex_count: 10,
index_offset: 0,
index_count: 30,
model: IDENTITY,
texture_slot: 5,
normal_map_slot: 2,
material: MaterialUniforms::DEFAULT,
visible: true,
joint_count: 4,
local_bb_min: [-1.0, -1.0, -1.0],
local_bb_max: [1.0, 1.0, 1.0],
lod_alternates: Vec::new(),
}
}
use serde::de::{self, Deserializer, Visitor};
pub(crate) fn len_handle_resolver(name: &str) -> Option<u32> {
if name.starts_with("unknown_") {
None
} else {
Some(name.len() as u32)
}
}
pub(crate) fn len_name_resolver(name: &str) -> u32 {
name.len() as u32
}
pub(crate) fn install_resolvers() {
crate::ecs::resolver::set_name_resolver(len_name_resolver);
crate::ecs::resolver::set_texture_handle_resolver(len_handle_resolver);
crate::ecs::resolver::set_audio_clip_handle_resolver(len_handle_resolver);
crate::ecs::resolver::set_font_handle_resolver(len_handle_resolver);
crate::ecs::resolver::set_mesh_handle_resolver(len_handle_resolver);
crate::ecs::resolver::set_material_handle_resolver(len_handle_resolver);
crate::ecs::resolver::set_skinned_mesh_handle_resolver(len_handle_resolver);
crate::ecs::resolver::set_shader_handle_resolver(len_handle_resolver);
}
pub(crate) struct NoneDeserializer;
impl<'de> Deserializer<'de> for NoneDeserializer {
type Error = de::value::Error;
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_none()
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
bytes byte_buf option unit unit_struct newtype_struct seq tuple
tuple_struct map struct enum identifier ignored_any
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::sync::atomic::{AtomicU32, Ordering};
#[test]
fn a_single_run_pass_drives_the_body_once() {
let runs = AtomicU32::new(0);
bench(Pace::Once, "test/once", 4, || {
runs.fetch_add(1, Ordering::Relaxed)
});
assert_eq!(runs.load(Ordering::Relaxed), 1);
}
#[test]
fn a_measured_pass_calibrates_then_times_and_counts() {
let runs = AtomicU32::new(0);
bench_over(1_000, Pace::Timed, "test/timed", 1, || {
runs.fetch_add(1, Ordering::Relaxed)
});
assert!(
runs.load(Ordering::Relaxed) >= 3,
"a measured pass runs the body far more than a single-run one"
);
}
#[test]
fn a_body_that_fills_the_window_stays_at_one_iteration() {
let runs = AtomicU32::new(0);
bench_over(0, Pace::Timed, "test/wide", 0, || {
runs.fetch_add(1, Ordering::Relaxed)
});
assert_eq!(
runs.load(Ordering::Relaxed),
3,
"one calibration pass, one timed, one counted"
);
}
}