use std::{
borrow::Cow,
fmt::Write,
sync::{Arc, LazyLock},
};
use scc::{
hash_map::{Entry, VacantEntry},
HashMap,
};
use super::*;
static NAMES: &str = include_str!("../../data/botw_hashed_names.txt");
static NUMBERED_NAMES: &str = include_str!("../../data/botw_numbered_names.txt");
type StringBuffer = crate::types::FixedSafeString<256>;
impl<const N: usize> Write for crate::types::FixedSafeString<N> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
match s
.len()
.min(N.saturating_sub(self.len).saturating_sub(s.len()))
{
0 => Ok(()),
len => {
self.data[self.len..self.len + len].copy_from_slice(s.as_bytes());
self.len += len;
Ok(())
}
}
}
}
struct ChildFormatIterator<'a, 'b> {
string: &'a str,
pos: usize,
index: usize,
buf: &'b mut StringBuffer,
}
impl<'a, 'b> ChildFormatIterator<'a, 'b> {
pub fn new(string: &'a str, pos: usize, buf: &'b mut StringBuffer) -> Self {
ChildFormatIterator {
string,
pos,
index: 0,
buf,
}
}
}
impl<'a, 'b> Iterator for ChildFormatIterator<'a, 'b> {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
self.buf.clear(); use std::fmt::Write;
let result = match self.index {
0 => write!(self.buf, "{}{}", self.string, self.pos),
1 => write!(self.buf, "{}{:02}", self.string, self.pos),
2 => write!(self.buf, "{}{:03}", self.string, self.pos),
3 => write!(self.buf, "{}_{}", self.string, self.pos),
4 => write!(self.buf, "{}_{:02}", self.string, self.pos),
5 => write!(self.buf, "{}_{:03}", self.string, self.pos),
_ => return None,
};
self.index += 1;
result.ok().map(|_| hash_name(self.buf.as_str()))
}
}
impl ExactSizeIterator for ChildFormatIterator<'_, '_> {
fn len(&self) -> usize {
6
}
}
fn format_numbered_name(name: &str, pos: usize, buf: &mut StringBuffer) {
buf.clear();
if name.contains("%d") {
let mut split = name.split("%d");
let prefix = unsafe { split.next().unwrap_unchecked() };
buf.insert_str(0, prefix);
write!(buf, "{}", pos).expect("Format failure");
if let Some(suffix) = split.next() {
buf.push_str(suffix);
}
} else if name.contains("%02d") {
let mut split = name.split("%02d");
let prefix = unsafe { split.next().unwrap_unchecked() };
buf.insert_str(0, prefix);
write!(buf, "{:02}", pos).expect("Format failure");
if let Some(suffix) = split.next() {
buf.push_str(suffix);
}
} else if name.contains("%03d") {
let mut split = name.split("%03d");
let prefix = unsafe { split.next().unwrap_unchecked() };
buf.insert_str(0, prefix);
write!(buf, "{:03}", pos).expect("Format failure");
if let Some(suffix) = split.next() {
buf.push_str(suffix);
}
} else if name.contains("%04d") {
let mut split = name.split("%04d");
let prefix = unsafe { split.next().unwrap_unchecked() };
buf.insert_str(0, prefix);
write!(buf, "{:04}", pos).expect("Format failure");
if let Some(suffix) = split.next() {
buf.push_str(suffix);
}
} else if name.contains("%u") {
let mut split = name.split("%u");
let prefix = unsafe { split.next().unwrap_unchecked() };
buf.insert_str(0, prefix);
write!(buf, "{}", pos).expect("Format failure");
if let Some(suffix) = split.next() {
buf.push_str(suffix);
}
} else if name.contains("%02u") {
let mut split = name.split("%02u");
let prefix = unsafe { split.next().unwrap_unchecked() };
buf.insert_str(0, prefix);
write!(buf, "{:02}", pos).expect("Format failure");
if let Some(suffix) = split.next() {
buf.push_str(suffix);
}
} else {
unsafe { core::hint::unreachable_unchecked() }
}
}
macro_rules! free_cow {
($cow:expr, $life:tt) => {{
let cow = $cow as *const _;
unsafe { &*(cow as *const Cow<$life, str>) }
}};
}
#[derive(Default)]
pub struct NameTable<'a> {
names: HashMap<u32, Cow<'a, str>, rustc_hash::FxBuildHasher>,
}
impl<'a> NameTable<'a> {
pub fn new(botw_strings: bool) -> NameTable<'a> {
if botw_strings {
Self {
names: {
let map = HashMap::with_capacity_and_hasher(58469, rustc_hash::FxBuildHasher);
for line in NAMES.lines() {
let _ = map.insert(hash_name(line), line.into());
}
map
},
}
} else {
Default::default()
}
}
pub fn add_name(&self, name: impl Into<Cow<'a, str>>) {
let name = name.into();
let hash = hash_name(&name);
self.names.entry(hash).or_insert(name);
}
pub fn add_name_with_hash(&self, name: impl Into<Cow<'a, str>>, hash: u32) {
self.names.entry(hash).or_insert_with(|| name.into());
}
pub fn add_name_str<'s: 'a>(&'a self, name: &'s str) {
let hash = hash_name(name);
self.names.entry(hash).or_insert_with(|| name.into());
}
pub fn get_name(&self, hash: u32, index: usize, parent_hash: u32) -> Option<&Cow<'_, str>> {
fn test_names<'a: 'b, 'b, 'c>(
entry: VacantEntry<'b, u32, Cow<'a, str>, rustc_hash::FxBuildHasher>,
hash: u32,
index: usize,
prefix: &str,
buf: &'c mut StringBuffer,
) -> std::result::Result<
&'b Cow<'a, str>,
VacantEntry<'b, u32, Cow<'a, str>, rustc_hash::FxBuildHasher>,
> {
for i in index..(index + 1) {
for guess_hash in ChildFormatIterator::new(prefix, i, buf) {
if guess_hash == hash {
let name = entry.insert_entry(buf.to_string().into());
return Ok(free_cow!(name.get(), 'a));
}
}
}
Err(entry)
}
let parent_name = self.names.get(&parent_hash).map(|c| free_cow!(c.get(), 'a));
match self.names.entry(hash) {
Entry::Occupied(entry) => Some(free_cow!(entry.get(), 'a)),
Entry::Vacant(entry) => {
let mut entry = entry;
let mut guess_buffer = StringBuffer::default();
if let Some(parent_name) = parent_name
{
let guess = test_names(entry, hash, index, parent_name, &mut guess_buffer)
.or_else(|entry| {
test_names(entry, hash, index, "Children", &mut guess_buffer)
})
.or_else(|entry| test_names(entry, hash, index, "Child", &mut guess_buffer))
.or_else(|mut entry| {
for suffix in ["s", "es", "List"] {
if let Some(singular) = parent_name.strip_suffix(suffix) {
let guess =
test_names(entry, hash, index, singular, &mut guess_buffer);
match guess {
Ok(found) => return Ok(found),
Err(ret_entry) => entry = ret_entry,
}
}
}
Err(entry)
});
match guess {
Ok(found) => return Some(free_cow!(found, 'a)),
Err(ret_entry) => {
entry = ret_entry;
}
}
}
for format in NUMBERED_NAMES.lines() {
for i in 0..(index + 2) {
format_numbered_name(format, i, &mut guess_buffer);
if hash_name(&guess_buffer) == hash {
let name =
entry.insert_entry(Cow::Owned(guess_buffer.as_str().to_owned()));
return Some(free_cow!(name.get(), 'a));
}
}
}
None
}
}
}
}
static DEFAULT_NAME_TABLE: LazyLock<Arc<NameTable<'static>>> =
LazyLock::new(|| Arc::new(NameTable::new(true)));
pub fn get_default_name_table() -> &'static LazyLock<Arc<NameTable<'static>>> {
&DEFAULT_NAME_TABLE
}