use rbx_dom_weak::{types::Variant, Instance, Ustr, UstrMap};
use rbx_reflection::{PropertyKind, PropertySerialization, Scriptability};
use crate::{variant_eq::variant_eq, Project};
pub fn filter_properties<'inst>(
project: &Project,
inst: &'inst Instance,
) -> UstrMap<&'inst Variant> {
let mut map: Vec<(Ustr, &Variant)> = Vec::with_capacity(inst.properties.len());
filter_properties_preallocated(project, inst, &mut map);
map.into_iter().collect()
}
pub fn filter_properties_preallocated<'inst>(
project: &Project,
inst: &'inst Instance,
allocation: &mut Vec<(Ustr, &'inst Variant)>,
) {
let sync_unscriptable = project
.syncback_rules
.as_ref()
.and_then(|s| s.sync_unscriptable)
.unwrap_or(true);
let class_data = rbx_reflection_database::get()
.unwrap()
.classes
.get(inst.class.as_str());
let predicate = |prop_name: &Ustr, prop_value: &Variant| {
if matches!(prop_value, Variant::Ref(_) | Variant::UniqueId(_)) {
return true;
}
if !should_property_serialize(&inst.class, prop_name) {
return true;
}
if !sync_unscriptable {
if let Some(data) = class_data {
if let Some(prop_data) = data.properties.get(prop_name.as_str()) {
if matches!(prop_data.scriptability, Scriptability::None) {
return true;
}
}
}
}
false
};
if let Some(class_data) = class_data {
let defaults = &class_data.default_properties;
for (name, value) in &inst.properties {
if predicate(name, value) {
continue;
}
if let Some(default) = defaults.get(name.as_str()) {
if !variant_eq(value, default) {
allocation.push((*name, value));
}
} else {
allocation.push((*name, value));
}
}
} else {
for (name, value) in &inst.properties {
if predicate(name, value) {
continue;
}
allocation.push((*name, value));
}
}
}
fn should_property_serialize(class_name: &str, prop_name: &str) -> bool {
let database = rbx_reflection_database::get().unwrap();
let mut current_class_name = class_name;
loop {
let class_data = match database.classes.get(current_class_name) {
Some(data) => data,
None => return true,
};
if let Some(data) = class_data.properties.get(prop_name) {
log::trace!("found {class_name}.{prop_name} on {current_class_name}");
return match &data.kind {
PropertyKind::Alias { alias_for } => {
should_property_serialize(current_class_name, alias_for)
}
PropertyKind::Canonical { serialization } => {
!matches!(serialization, PropertySerialization::DoesNotSerialize)
}
kind => unimplemented!("unknown property kind {kind:?}"),
};
} else if let Some(super_class) = class_data.superclass.as_ref() {
current_class_name = super_class;
} else {
break;
}
}
true
}