Compare commits
No commits in common. "2b8d3942da04c224ff2586da794de9e93873f76a" and "15b8ec562c77c93717816d5cf8b584f8d3f21165" have entirely different histories.
2b8d3942da
...
15b8ec562c
|
@ -157,9 +157,24 @@ pub mod rust {
|
||||||
.packages
|
.packages
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(_, pkg)| {
|
.flat_map(|(_, pkg)| {
|
||||||
pkg.types
|
pkg.types.values().map(|ty| {
|
||||||
.values()
|
let name = match ty {
|
||||||
.map(|ty| (ty.obj_ref(), Self::get_prefixed_name(&ty)))
|
UnrealType::Class(class) => {
|
||||||
|
format!("U{}", canonicalize_name(&class.name))
|
||||||
|
}
|
||||||
|
UnrealType::Struct(class) => {
|
||||||
|
format!("F{}", canonicalize_name(&class.name))
|
||||||
|
}
|
||||||
|
UnrealType::Actor(class) => {
|
||||||
|
format!("A{}", canonicalize_name(&class.name))
|
||||||
|
}
|
||||||
|
UnrealType::Enum(class) => {
|
||||||
|
format!("E{}", canonicalize_name(&class.name))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(ty.obj_ref(), name)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect::<BTreeMap<_, _>>();
|
.collect::<BTreeMap<_, _>>();
|
||||||
|
|
||||||
|
@ -169,38 +184,6 @@ pub mod rust {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns the absolute path of a type with the assumption that all
|
|
||||||
/// packages are children of the path `::crate::sdk`
|
|
||||||
fn get_type_path(&self, key: &ObjectRef) -> Option<String> {
|
|
||||||
let pkg = &self.sdk.packages.get(&key.package)?.name;
|
|
||||||
self.get_type_name(key)
|
|
||||||
.map(|name| format!("::crate::sdk::{pkg}::{name}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// returns the precached, prefixed and cannonicalized (for this
|
|
||||||
/// language, Rust) name for this object-ref
|
|
||||||
fn get_type_name(&self, key: &ObjectRef) -> Option<String> {
|
|
||||||
self.type_name_cache.get(key).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// prefixes the typename according to its kind (UObject, AActor, FStruct, EEnum)
|
|
||||||
fn get_prefixed_name(ty: &UnrealType) -> String {
|
|
||||||
match ty {
|
|
||||||
UnrealType::Class(_) => {
|
|
||||||
format!("U{}", canonicalize_name(&ty.unique_name()))
|
|
||||||
}
|
|
||||||
UnrealType::Struct(_) => {
|
|
||||||
format!("F{}", canonicalize_name(&ty.unique_name()))
|
|
||||||
}
|
|
||||||
UnrealType::Actor(_) => {
|
|
||||||
format!("A{}", canonicalize_name(&ty.unique_name()))
|
|
||||||
}
|
|
||||||
UnrealType::Enum(_) => {
|
|
||||||
format!("E{}", canonicalize_name(&ty.unique_name()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> anyhow::Result<()> {
|
pub fn build(self) -> anyhow::Result<()> {
|
||||||
for pkg in self.sdk.packages.values() {
|
for pkg in self.sdk.packages.values() {
|
||||||
self.generate_package(pkg)?;
|
self.generate_package(pkg)?;
|
||||||
|
@ -212,61 +195,64 @@ pub mod rust {
|
||||||
fn type_name(&self, ty: &Type) -> anyhow::Result<String> {
|
fn type_name(&self, ty: &Type) -> anyhow::Result<String> {
|
||||||
let type_name = match ty {
|
let type_name = match ty {
|
||||||
Type::Ptr(inner) | Type::Ref(inner) => {
|
Type::Ptr(inner) | Type::Ref(inner) => {
|
||||||
format!(
|
format!("Option<NonNull<{}>>", self.type_name(&inner)?)
|
||||||
"::core::option::Option<NonNull<{}>>",
|
|
||||||
self.type_name(&inner)?
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Type::WeakPtr(inner) => {
|
Type::WeakPtr(inner) => {
|
||||||
format!(
|
format!(
|
||||||
"::crate::engine::TWeakObjectPtr<{}>",
|
"TWeakObjectPtr<{}>",
|
||||||
self.get_type_path(inner)
|
self.type_name_cache
|
||||||
|
.get(inner)
|
||||||
.context("type name was not cached.")?
|
.context("type name was not cached.")?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Type::SoftPtr(inner) => {
|
Type::SoftPtr(inner) => {
|
||||||
format!(
|
format!(
|
||||||
"::crate::engine::TSoftObjectPtr<{}>",
|
"TSoftObjectPtr<{}>",
|
||||||
self.get_type_path(inner)
|
self.type_name_cache
|
||||||
|
.get(inner)
|
||||||
.context("type name was not cached.")?
|
.context("type name was not cached.")?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Type::LazyPtr(inner) => {
|
Type::LazyPtr(inner) => {
|
||||||
format!(
|
format!(
|
||||||
"::crate::engine::TLazyObjectPtr<{}>",
|
"TLazyObjectPtr<{}>",
|
||||||
self.get_type_path(inner)
|
self.type_name_cache
|
||||||
|
.get(inner)
|
||||||
.context("type name was not cached.")?
|
.context("type name was not cached.")?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Type::AssetPtr(inner) => format!(
|
Type::AssetPtr(inner) => format!(
|
||||||
"::crate::engine::TAssetPtr<{}>",
|
"TAssetPtr<{}>",
|
||||||
self.get_type_path(inner)
|
self.type_name_cache
|
||||||
|
.get(inner)
|
||||||
.context("type name was not cached.")?
|
.context("type name was not cached.")?
|
||||||
),
|
),
|
||||||
Type::Array(inner) => {
|
Type::Array(inner) => format!("TArray<{}>", self.type_name(&inner)?),
|
||||||
format!("::crate::engine::TArray<{}>", self.type_name(&inner)?)
|
|
||||||
}
|
|
||||||
Type::Primitive(prim) => {
|
Type::Primitive(prim) => {
|
||||||
format!("{prim}")
|
format!("{prim}")
|
||||||
}
|
}
|
||||||
Type::RawArray { ty, len } => {
|
Type::RawArray { ty, len } => {
|
||||||
format!("[{}; {}]", self.type_name(&ty)?, len)
|
format!("[{}; {}]", self.type_name(&ty)?, len)
|
||||||
}
|
}
|
||||||
Type::Name => "::crate::engine::FName".to_string(),
|
Type::Name => "FName".to_string(),
|
||||||
Type::String => "::crate::engine::FString".to_string(),
|
Type::String => "FString".to_string(),
|
||||||
Type::Text => "::crate::engine::FText".to_string(),
|
Type::Text => "FText".to_string(),
|
||||||
Type::Enum { enum_type, .. } => self
|
Type::Enum { enum_type, .. } => self
|
||||||
.get_type_path(enum_type)
|
.type_name_cache
|
||||||
.context("type name was not cached.")?,
|
.get(enum_type)
|
||||||
|
.context("type name was not cached.")?
|
||||||
|
.clone(),
|
||||||
Type::Class(class) => {
|
Type::Class(class) => {
|
||||||
format!(
|
format!(
|
||||||
"::core::option::Option<{}>",
|
"::core::option::Option<{}>",
|
||||||
self.get_type_path(class)
|
self.type_name_cache
|
||||||
|
.get(class)
|
||||||
.context("type name was not cached.")?
|
.context("type name was not cached.")?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Type::Struct(class) => self
|
Type::Struct(class) => self
|
||||||
.get_type_path(class)
|
.type_name_cache
|
||||||
|
.get(class)
|
||||||
.context("type name was not cached.")?
|
.context("type name was not cached.")?
|
||||||
.clone(),
|
.clone(),
|
||||||
};
|
};
|
||||||
|
@ -276,7 +262,8 @@ pub mod rust {
|
||||||
|
|
||||||
fn generate_enum(&self, enum0: &Enum) -> anyhow::Result<TokenStream> {
|
fn generate_enum(&self, enum0: &Enum) -> anyhow::Result<TokenStream> {
|
||||||
let name = self
|
let name = self
|
||||||
.get_type_name(&enum0.obj_ref)
|
.type_name_cache
|
||||||
|
.get(&enum0.obj_ref)
|
||||||
.context("enum name was not previously canonicalized and cached.")?;
|
.context("enum name was not previously canonicalized and cached.")?;
|
||||||
|
|
||||||
let variants = enum0.values.iter().map(|(&value, name)| {
|
let variants = enum0.values.iter().map(|(&value, name)| {
|
||||||
|
@ -424,13 +411,13 @@ pub mod rust {
|
||||||
struct_name: &str,
|
struct_name: &str,
|
||||||
method: &ClassMethod,
|
method: &ClassMethod,
|
||||||
) -> anyhow::Result<(TokenStream, TokenStream)> {
|
) -> anyhow::Result<(TokenStream, TokenStream)> {
|
||||||
let method_name = canonicalize_name(&method.unique_name());
|
let method_name = canonicalize_name(&method.name);
|
||||||
|
|
||||||
let parameters = method
|
let parameters = method
|
||||||
.parameters
|
.parameters
|
||||||
.iter()
|
.iter()
|
||||||
.map(|parameter| {
|
.map(|parameter| {
|
||||||
let name = canonicalize_name(¶meter.unique_name());
|
let name = canonicalize_name(¶meter.name);
|
||||||
let type_name = self.type_name(¶meter.ty)?;
|
let type_name = self.type_name(¶meter.ty)?;
|
||||||
|
|
||||||
anyhow::Ok((parameter, name, type_name))
|
anyhow::Ok((parameter, name, type_name))
|
||||||
|
@ -590,16 +577,14 @@ pub mod rust {
|
||||||
fn generate_struct_ctor(
|
fn generate_struct_ctor(
|
||||||
&self,
|
&self,
|
||||||
_class: &Class,
|
_class: &Class,
|
||||||
type_name: &str,
|
_name: &str,
|
||||||
fields: &Vec<(&ClassField, Cow<str>, String)>,
|
fields: &Vec<(&ClassField, Cow<str>, String)>,
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
let fields_defs = fields.iter().map(|(_, name, ty)| quote! {#name: #ty});
|
let fields_defs = fields.iter().map(|(_, name, ty)| quote! {#name: #ty});
|
||||||
|
|
||||||
let this_field_asignments = fields.iter().map(|(_, name, _ty)| {
|
let this_field_asignments = fields.iter().map(|(_, name, _ty)| {
|
||||||
let setter = format_ident!("set_{}", name);
|
let setter = format_ident!("set_{}", name);
|
||||||
let field_trait = format_ident!("{type_name}Fields");
|
quote! {this.#setter(#name);}
|
||||||
|
|
||||||
quote! {<Self as #field_trait>::#setter(this, #name);}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// FIXME: handle super struct fields aswell, ARK doesnt seem to have those anyways.
|
// FIXME: handle super struct fields aswell, ARK doesnt seem to have those anyways.
|
||||||
|
@ -626,7 +611,7 @@ pub mod rust {
|
||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|field| {
|
.map(|field| {
|
||||||
let name = canonicalize_name(&field.unique_name());
|
let name = canonicalize_name(&field.name);
|
||||||
let ty = self.type_name(&field.ty)?;
|
let ty = self.type_name(&field.ty)?;
|
||||||
|
|
||||||
anyhow::Ok((field, name, ty))
|
anyhow::Ok((field, name, ty))
|
||||||
|
@ -673,8 +658,9 @@ pub mod rust {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_class(&self, class: &Class) -> anyhow::Result<TokenStream> {
|
fn generate_class(&self, class: &Class) -> anyhow::Result<TokenStream> {
|
||||||
let name = &self
|
let name = self
|
||||||
.get_type_name(&class.obj_ref)
|
.type_name_cache
|
||||||
|
.get(&class.obj_ref)
|
||||||
.context("enum name was not previously canonicalized and cached.")?;
|
.context("enum name was not previously canonicalized and cached.")?;
|
||||||
|
|
||||||
let (field_trait, ctor) = self.generate_struct_fields(class, name)?;
|
let (field_trait, ctor) = self.generate_struct_fields(class, name)?;
|
||||||
|
@ -698,6 +684,8 @@ pub mod rust {
|
||||||
impl #method_trait for #name {}
|
impl #method_trait for #name {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: impl super-type fields and methods.
|
||||||
|
|
||||||
let mut sup = class.super_class;
|
let mut sup = class.super_class;
|
||||||
let super_traits = core::iter::from_fn(|| {
|
let super_traits = core::iter::from_fn(|| {
|
||||||
if let Some(key) = sup {
|
if let Some(key) = sup {
|
||||||
|
@ -708,12 +696,13 @@ pub mod rust {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// SAFETY: we already got this type by its obj_ref, so it must be there.
|
.map(|ty| ty.unique_name())
|
||||||
.map(|ty| self.get_type_path(&ty.obj_ref()).unwrap())
|
|
||||||
.map(|super_name| {
|
.map(|super_name| {
|
||||||
let fields = format_ident!("{super_name}Fields");
|
let fields = format_ident!("{super_name}Fields");
|
||||||
let methods = format_ident!("{super_name}Methods");
|
let methods = format_ident!("{super_name}Methods");
|
||||||
|
|
||||||
|
// FIXME: full path here? meaning I need the package name aswell.
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl #fields for #name {}
|
impl #fields for #name {}
|
||||||
impl #methods for #name {}
|
impl #methods for #name {}
|
||||||
|
@ -740,7 +729,8 @@ pub mod rust {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_package(&self, pkg: &ProcessedPackage) -> anyhow::Result<()> {
|
fn generate_package(&self, pkg: &ProcessedPackage) -> anyhow::Result<()> {
|
||||||
let pkg_name = canonicalize_name(&pkg.name);
|
// TODO: canonicalize_name(&pkg.name);
|
||||||
|
let pkg_name = "PACKAGE_NAME_PLACEHOLDER".to_string();
|
||||||
|
|
||||||
for (_id, ty) in &pkg.types {
|
for (_id, ty) in &pkg.types {
|
||||||
let tokens = match ty {
|
let tokens = match ty {
|
||||||
|
|
|
@ -16,4 +16,3 @@ pub mod v2_types;
|
||||||
|
|
||||||
pub mod any_type;
|
pub mod any_type;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod util;
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ use ron::ser::PrettyConfig;
|
||||||
|
|
||||||
use crate::global_tables::objects::GOBJECTS;
|
use crate::global_tables::objects::GOBJECTS;
|
||||||
use crate::sdk::repr::*;
|
use crate::sdk::repr::*;
|
||||||
use crate::util::DedupIter;
|
|
||||||
use crate::v2_types::{
|
use crate::v2_types::{
|
||||||
any_type::{self, AnyField, AnyObject, AnyProperty, AnyStruct},
|
any_type::{self, AnyField, AnyObject, AnyProperty, AnyStruct},
|
||||||
traits::{
|
traits::{
|
||||||
|
@ -239,28 +238,6 @@ impl Class {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let fields = fields
|
|
||||||
.into_iter()
|
|
||||||
.dedup_with_by(
|
|
||||||
|n, field| ClassField {
|
|
||||||
override_name: Some(format!("{}{}", field.name, n)),
|
|
||||||
..field
|
|
||||||
},
|
|
||||||
|field: &ClassField| -> String { field.name.clone() },
|
|
||||||
)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let methods = methods
|
|
||||||
.into_iter()
|
|
||||||
.dedup_with_by(
|
|
||||||
|n, field| ClassMethod {
|
|
||||||
override_name: Some(format!("{}{}", field.name, n)),
|
|
||||||
..field
|
|
||||||
},
|
|
||||||
|field: &ClassMethod| -> String { field.name.clone() },
|
|
||||||
)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
(fields, methods)
|
(fields, methods)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,16 +257,14 @@ impl Class {
|
||||||
|
|
||||||
impl ClassField {
|
impl ClassField {
|
||||||
pub fn from_uprop(prop: UProperty) -> anyhow::Result<Self> {
|
pub fn from_uprop(prop: UProperty) -> anyhow::Result<Self> {
|
||||||
let name = prop
|
|
||||||
.name()
|
|
||||||
.get_name()
|
|
||||||
.context("failed to retrieve field name")?;
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
offset: *prop.offset() as u32,
|
offset: *prop.offset() as u32,
|
||||||
size: *prop.element_size() as u32,
|
size: *prop.element_size() as u32,
|
||||||
flags: *prop.property_flags(),
|
flags: *prop.property_flags(),
|
||||||
override_name: None,
|
name: prop
|
||||||
name,
|
.name()
|
||||||
|
.get_name()
|
||||||
|
.context("failed to retrieve field name")?,
|
||||||
ty: resolve_type(prop).context("failed to get field type")?,
|
ty: resolve_type(prop).context("failed to get field type")?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -313,21 +288,11 @@ impl ClassMethod {
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.map(|prop| ClassField::from_uprop(prop))
|
.map(|prop| ClassField::from_uprop(prop))
|
||||||
.collect::<anyhow::Result<Vec<_>>>()?
|
.collect::<anyhow::Result<Vec<_>>>()?;
|
||||||
.into_iter()
|
|
||||||
.dedup_with_by(
|
|
||||||
|n, field| ClassField {
|
|
||||||
override_name: Some(format!("{}{}", field.name, n)),
|
|
||||||
..field
|
|
||||||
},
|
|
||||||
|field: &ClassField| -> String { field.name.clone() },
|
|
||||||
)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(ClassMethod {
|
Ok(ClassMethod {
|
||||||
name,
|
name,
|
||||||
full_name,
|
full_name,
|
||||||
override_name: None,
|
|
||||||
flags: *func.function_flags(),
|
flags: *func.function_flags(),
|
||||||
parameters: params,
|
parameters: params,
|
||||||
})
|
})
|
||||||
|
@ -394,14 +359,6 @@ impl Package {
|
||||||
.context("not actually a package object")?,
|
.context("not actually a package object")?,
|
||||||
types,
|
types,
|
||||||
dependencies,
|
dependencies,
|
||||||
name: self
|
|
||||||
.package_object
|
|
||||||
.get_name()
|
|
||||||
.context("failed to get package object name.")?,
|
|
||||||
full_name: self
|
|
||||||
.package_object
|
|
||||||
.get_full_name()
|
|
||||||
.context("failed to get package object name.")?,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,8 +55,6 @@ pub struct Package {
|
||||||
pub struct ProcessedPackage {
|
pub struct ProcessedPackage {
|
||||||
/// the package object ref.
|
/// the package object ref.
|
||||||
pub package_object: PackageRef,
|
pub package_object: PackageRef,
|
||||||
pub name: String,
|
|
||||||
pub full_name: String,
|
|
||||||
/// all types extracted from this package referenced by their `ObjectRef`.
|
/// all types extracted from this package referenced by their `ObjectRef`.
|
||||||
pub types: BTreeMap<ObjectRef, UnrealType>,
|
pub types: BTreeMap<ObjectRef, UnrealType>,
|
||||||
/// All other packages that types in this package depend on directly.
|
/// All other packages that types in this package depend on directly.
|
||||||
|
@ -144,7 +142,6 @@ pub struct ClassField {
|
||||||
pub offset: u32,
|
pub offset: u32,
|
||||||
pub size: u32,
|
pub size: u32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub override_name: Option<String>,
|
|
||||||
pub flags: EPropertyFlags,
|
pub flags: EPropertyFlags,
|
||||||
pub ty: Type,
|
pub ty: Type,
|
||||||
}
|
}
|
||||||
|
@ -169,19 +166,11 @@ impl ClassField {
|
||||||
pub fn is_rep(&self) -> bool {
|
pub fn is_rep(&self) -> bool {
|
||||||
!self.flags.contains(EPropertyFlags::RepSkip)
|
!self.flags.contains(EPropertyFlags::RepSkip)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unique_name(&self) -> &str {
|
|
||||||
self.override_name
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
.unwrap_or(self.name.as_str())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct ClassMethod {
|
pub struct ClassMethod {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub override_name: Option<String>,
|
|
||||||
pub full_name: String,
|
pub full_name: String,
|
||||||
pub flags: EFunctionFlags,
|
pub flags: EFunctionFlags,
|
||||||
pub parameters: Vec<ClassField>,
|
pub parameters: Vec<ClassField>,
|
||||||
|
@ -191,23 +180,13 @@ impl ClassMethod {
|
||||||
pub fn is_static(&self) -> bool {
|
pub fn is_static(&self) -> bool {
|
||||||
self.flags.contains(EFunctionFlags::Static)
|
self.flags.contains(EFunctionFlags::Static)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_native(&self) -> bool {
|
pub fn is_native(&self) -> bool {
|
||||||
self.flags.contains(EFunctionFlags::Native)
|
self.flags.contains(EFunctionFlags::Native)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// this function is replicated to the server, might be exploitable.
|
/// this function is replicated to the server, might be exploitable.
|
||||||
pub fn is_net_server(&self) -> bool {
|
pub fn is_net_server(&self) -> bool {
|
||||||
self.flags.contains(EFunctionFlags::NetServer)
|
self.flags.contains(EFunctionFlags::NetServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unique_name(&self) -> &str {
|
|
||||||
self.override_name
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
.unwrap_or(self.name.as_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn out_params(&self) -> Vec<&ClassField> {
|
pub fn out_params(&self) -> Vec<&ClassField> {
|
||||||
self.parameters
|
self.parameters
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -1,210 +0,0 @@
|
||||||
use std::collections::hash_map::Entry;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::hash::Hash;
|
|
||||||
|
|
||||||
pub struct DedupIdentity;
|
|
||||||
|
|
||||||
pub trait DedupGetHash<T> {
|
|
||||||
type Hashed: Hash + Eq;
|
|
||||||
fn get_hash<'a>(&mut self, a: &'a T) -> Self::Hashed;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> DedupGetHash<T> for DedupIdentity
|
|
||||||
where
|
|
||||||
T: Hash + Eq + Clone,
|
|
||||||
{
|
|
||||||
type Hashed = T;
|
|
||||||
|
|
||||||
fn get_hash<'a>(&mut self, a: &'a T) -> Self::Hashed {
|
|
||||||
a.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F, T, H> DedupGetHash<T> for F
|
|
||||||
where
|
|
||||||
F: FnMut(&T) -> H,
|
|
||||||
H: Hash + Eq,
|
|
||||||
{
|
|
||||||
type Hashed = H;
|
|
||||||
|
|
||||||
fn get_hash<'a>(&mut self, a: &'a T) -> Self::Hashed {
|
|
||||||
self(a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DedupWithBy<I, F, H>
|
|
||||||
where
|
|
||||||
I: Iterator,
|
|
||||||
F: FnMut(usize, <I as Iterator>::Item) -> <I as Iterator>::Item,
|
|
||||||
H: DedupGetHash<<I as Iterator>::Item>,
|
|
||||||
{
|
|
||||||
iter: I,
|
|
||||||
/// makes item unique, is told how many elements of this equality.
|
|
||||||
cb_fn: F,
|
|
||||||
/// returns the borrowed element by which to compare the iterators elements
|
|
||||||
hash_fn: H,
|
|
||||||
cache: HashMap<H::Hashed, usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DedupIter: Iterator {
|
|
||||||
fn dedup_with<'a, F>(self, f: F) -> DedupWithBy<Self, F, DedupIdentity>
|
|
||||||
where
|
|
||||||
F: FnMut(usize, <Self as Iterator>::Item) -> <Self as Iterator>::Item,
|
|
||||||
Self: Sized,
|
|
||||||
<Self as Iterator>::Item: Eq + Hash + Clone,
|
|
||||||
{
|
|
||||||
DedupWithBy {
|
|
||||||
iter: self,
|
|
||||||
cb_fn: f,
|
|
||||||
hash_fn: DedupIdentity,
|
|
||||||
cache: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dedup_with_by<'a, F, H>(self, f: F, h: H) -> DedupWithBy<Self, F, H>
|
|
||||||
where
|
|
||||||
F: FnMut(usize, <Self as Iterator>::Item) -> <Self as Iterator>::Item,
|
|
||||||
H: DedupGetHash<<Self as Iterator>::Item>,
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
DedupWithBy {
|
|
||||||
iter: self,
|
|
||||||
cb_fn: f,
|
|
||||||
hash_fn: h,
|
|
||||||
cache: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I> DedupIter for I where I: Iterator {}
|
|
||||||
|
|
||||||
impl<I, F, H> Iterator for DedupWithBy<I, F, H>
|
|
||||||
where
|
|
||||||
I: Iterator,
|
|
||||||
F: FnMut(usize, <I as Iterator>::Item) -> <I as Iterator>::Item,
|
|
||||||
H: DedupGetHash<<I as Iterator>::Item>,
|
|
||||||
{
|
|
||||||
type Item = <I as Iterator>::Item;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let mut next = self.iter.next()?;
|
|
||||||
|
|
||||||
let next = loop {
|
|
||||||
let hash = self.hash_fn.get_hash(&next);
|
|
||||||
|
|
||||||
match self.cache.entry(hash) {
|
|
||||||
Entry::Occupied(mut entry) => {
|
|
||||||
*entry.get_mut() += 1;
|
|
||||||
|
|
||||||
next = (self.cb_fn)(*entry.get(), next);
|
|
||||||
}
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
entry.insert(0);
|
|
||||||
break next;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(next)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn numbers() {
|
|
||||||
let numbers = [1, 2, 2, 2, 3, 4, 5, 6, 22];
|
|
||||||
|
|
||||||
let unique = numbers
|
|
||||||
.into_iter()
|
|
||||||
.dedup_with(|n, i| (i * 10 + n) as usize)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
assert_eq!(&unique, &[1, 2, 21, 22, 3, 4, 5, 6, 221]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
struct Key {
|
|
||||||
number: usize,
|
|
||||||
other: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn keys() {
|
|
||||||
let keys = [
|
|
||||||
Key {
|
|
||||||
number: 1,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
number: 2,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
number: 2,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
number: 2,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
number: 3,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
number: 4,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
number: 5,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
let expected = [
|
|
||||||
Key {
|
|
||||||
number: 1,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
number: 2,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
number: 21,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
number: 22,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
number: 3,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
number: 4,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
number: 5,
|
|
||||||
other: 1.0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let unique = keys
|
|
||||||
.into_iter()
|
|
||||||
.dedup_with_by(
|
|
||||||
|n, key| Key {
|
|
||||||
number: (key.number * 10 + n),
|
|
||||||
..key
|
|
||||||
},
|
|
||||||
|key: &Key| -> usize { key.number },
|
|
||||||
)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
assert_eq!(&unique, &expected);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue