unreal-sdk/src/global_tables/names.rs
2023-04-17 18:46:27 +02:00

179 lines
4.8 KiB
Rust

use std::{ops::Index, ptr::NonNull, sync::RwLock};
use crate::core_types::FName;
lazy_static::lazy_static! {
pub static ref GNAMES: RwLock<GNames> = RwLock::new(GNames::new());
}
#[derive(Debug, Default)]
pub struct GNames {
names: Option<NonNull<NameEntryArray>>,
}
impl GNames {
pub const fn new() -> Self {
Self { names: None }
}
pub fn set_names(&mut self, names: NonNull<NameEntryArray>) {
self.names = Some(names);
}
pub fn as_names(&self) -> Option<&NameEntryArray> {
self.names.map(|names| unsafe { names.as_ref() })
}
}
unsafe impl Send for GNames {}
unsafe impl Sync for GNames {}
#[repr(C)]
#[derive(Debug)]
pub struct FNameEntry {
pub index: u32,
pub hash_next: *const FNameEntry,
pub ansi_name: [u8; 1024],
}
impl FNameEntry {
/// panics if the name cant be turned into a string
pub fn as_string(&self) -> String {
self.try_into().unwrap()
}
pub fn as_str(&self) -> &str {
let end = self.ansi_name.iter().position(|&c| c == 0);
let bytes = &self.ansi_name[..end.unwrap_or(self.ansi_name.len())];
// TODO: debug assert this
unsafe { std::str::from_utf8_unchecked(bytes) }
}
}
impl<'a> TryInto<String> for &'a FNameEntry {
type Error = std::string::FromUtf8Error;
fn try_into(self) -> Result<String, Self::Error> {
let bytes = self
.ansi_name
.iter()
.take_while(|&&b| b != 0)
.map(|&b| b)
.collect::<Vec<_>>();
String::from_utf8(bytes)
}
}
const NAMES_ELEMENTS_PER_CHUNK: usize = 16 * 1024;
const NAMES_CHUNK_TABLE_SIZE: usize =
(2 * 1024 * 1024 + NAMES_ELEMENTS_PER_CHUNK - 1) / NAMES_ELEMENTS_PER_CHUNK;
#[repr(C)]
#[derive(Debug)]
pub struct NameEntryArray {
chunks: [Option<NonNull<Option<NonNull<FNameEntry>>>>; NAMES_CHUNK_TABLE_SIZE],
num_elements: u32,
num_chunks: u32,
}
unsafe impl Send for NameEntryArray {}
unsafe impl Sync for NameEntryArray {}
impl NameEntryArray {
/// this does not mean that the name at this index is actually present!, just that looking it up is sound, because all the indices point at allocated memory
pub fn is_valid_index(&self, index: usize) -> bool {
index < self.num_elements as usize
}
/// panics if chunk_index is out of bounds
fn chunk_as_slice(&self, chunk_index: usize) -> Option<&[Option<NonNull<FNameEntry>>]> {
// TODO: probably make this unchecked for release?
self.chunks
// get chunk
[chunk_index]
// cast chunk pointer into slice of FNameEntry pointers
.map(|ptr| unsafe {
core::slice::from_raw_parts(ptr.as_ptr(), NAMES_ELEMENTS_PER_CHUNK)
})
}
pub fn get_index(&self, index: usize) -> Option<&FNameEntry> {
if index < self.num_elements as usize {
let chunk_index = index / NAMES_ELEMENTS_PER_CHUNK;
let within_chunk_index = index % NAMES_ELEMENTS_PER_CHUNK;
// safety: we know chunk_index and within_chunk_index are valid (or do we?);
// TODO: make sure this is actually the case
// it is because index is < than self.num_elements
self.chunk_as_slice(chunk_index)?[within_chunk_index].map(|ptr| unsafe { ptr.as_ref() })
} else {
None
}
}
pub fn iter(&self) -> NamesIterator {
NamesIterator::new(self)
}
pub fn len(&self) -> usize {
self.num_elements as usize
}
pub fn fname_to_string(&self, name: &FName) -> anyhow::Result<String> {
use anyhow::Context;
Ok(self
.get_index(name.comparison_index as usize)
.context("invalid comparison index")?
.try_into()?)
}
}
impl Index<usize> for NameEntryArray {
type Output = FNameEntry;
fn index(&self, i: usize) -> &Self::Output {
self.get_index(i).expect("Out of bounds access")
}
}
pub struct NamesIterator<'a> {
names: &'a NameEntryArray,
index: usize,
}
impl<'a> NamesIterator<'a> {
pub fn new(names: &'a NameEntryArray) -> Self {
Self { names, index: 0 }
}
}
impl<'a> Iterator for NamesIterator<'a> {
type Item = &'a FNameEntry;
fn next(&mut self) -> Option<Self::Item> {
loop {
if !self.names.is_valid_index(self.index) {
break None;
}
let item = self.names.get_index(self.index);
self.index += 1;
// skip empty entries, we dont care about them
if item.is_some() {
break item;
}
}
}
}
impl<'a> IntoIterator for &'a NameEntryArray {
type Item = &'a FNameEntry;
type IntoIter = NamesIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
NamesIterator {
names: self,
index: 0,
}
}
}