1use std::{borrow::Cow, collections::BTreeMap, io::Write};
2
3use ahash::{HashMap, HashMapExt};
4use rbx_dom_weak::{
5 types::{Ref, SharedString, SharedStringHash, Variant, VariantType},
6 WeakDom,
7};
8use rbx_reflection::{DataType, PropertyKind, PropertySerialization, ReflectionDatabase};
9
10use crate::{
11 conversion::ConvertVariant,
12 core::find_serialized_property_descriptor,
13 error::{EncodeError as NewEncodeError, EncodeErrorKind},
14 types::write_value_xml,
15};
16
17use crate::serializer_core::{XmlEventWriter, XmlWriteEvent};
18
19pub fn encode_internal<W: Write>(
20 output: W,
21 tree: &WeakDom,
22 ids: &[Ref],
23 options: EncodeOptions,
24) -> Result<(), NewEncodeError> {
25 let mut writer = XmlEventWriter::from_output(output);
26 let mut state = EmitState::new(options);
27
28 writer.write(XmlWriteEvent::start_element("roblox").attr("version", "4"))?;
29
30 let mut property_buffer = Vec::new();
31 for id in ids {
32 serialize_instance(&mut writer, &mut state, tree, *id, &mut property_buffer)?;
33 }
34
35 serialize_shared_strings(&mut writer, &mut state)?;
36
37 writer.write(XmlWriteEvent::end_element())?;
38
39 Ok(())
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44#[non_exhaustive]
45pub enum EncodePropertyBehavior {
46 IgnoreUnknown,
50
51 WriteUnknown,
58
59 ErrorOnUnknown,
62
63 NoReflection,
70}
71
72#[derive(Debug, Clone)]
74pub struct EncodeOptions<'db> {
75 property_behavior: EncodePropertyBehavior,
76 database: &'db ReflectionDatabase<'db>,
77}
78
79impl<'db> EncodeOptions<'db> {
80 #[inline]
82 pub fn new() -> Self {
83 EncodeOptions {
84 property_behavior: EncodePropertyBehavior::IgnoreUnknown,
85 database: rbx_reflection_database::get().unwrap(),
86 }
87 }
88
89 #[inline]
92 pub fn property_behavior(self, property_behavior: EncodePropertyBehavior) -> Self {
93 EncodeOptions {
94 property_behavior,
95 ..self
96 }
97 }
98
99 #[inline]
102 pub fn reflection_database(self, database: &'db ReflectionDatabase<'db>) -> Self {
103 EncodeOptions { database, ..self }
104 }
105
106 pub(crate) fn use_reflection(&self) -> bool {
107 self.property_behavior != EncodePropertyBehavior::NoReflection
108 }
109}
110
111impl<'db> Default for EncodeOptions<'db> {
112 fn default() -> EncodeOptions<'db> {
113 EncodeOptions::new()
114 }
115}
116
117pub struct EmitState<'db> {
118 options: EncodeOptions<'db>,
119
120 referent_map: HashMap<Ref, u32>,
123
124 next_referent: u32,
126
127 shared_strings_to_emit: BTreeMap<SharedStringHash, SharedString>,
130}
131
132impl<'db> EmitState<'db> {
133 pub fn new(options: EncodeOptions<'db>) -> EmitState<'db> {
134 EmitState {
135 options,
136 referent_map: HashMap::new(),
137 next_referent: 0,
138 shared_strings_to_emit: BTreeMap::new(),
139 }
140 }
141
142 pub fn map_id(&mut self, id: Ref) -> u32 {
143 match self.referent_map.get(&id) {
144 Some(&value) => value,
145 None => {
146 let referent = self.next_referent;
147 self.referent_map.insert(id, referent);
148 self.next_referent += 1;
149 referent
150 }
151 }
152 }
153
154 pub fn add_shared_string(&mut self, value: SharedString) {
155 self.shared_strings_to_emit.insert(value.hash(), value);
156 }
157}
158
159fn serialize_instance<'dom, W: Write>(
164 writer: &mut XmlEventWriter<W>,
165 state: &mut EmitState,
166 tree: &'dom WeakDom,
167 id: Ref,
168 property_buffer: &mut Vec<(&'dom str, &'dom Variant)>,
169) -> Result<(), NewEncodeError> {
170 let instance = tree.get_by_ref(id).unwrap();
171 let mapped_id = state.map_id(id);
172
173 writer.write(
174 XmlWriteEvent::start_element("Item")
175 .attr("class", &instance.class)
176 .attr("referent", &mapped_id.to_string()),
177 )?;
178
179 writer.write(XmlWriteEvent::start_element("Properties"))?;
180
181 write_value_xml(
182 writer,
183 state,
184 "Name",
185 &Variant::String(instance.name.clone()),
186 )?;
187
188 property_buffer.extend(instance.properties.iter().map(|(k, v)| (k.as_str(), v)));
191 property_buffer.sort_unstable_by_key(|(key, _)| *key);
192
193 for (property_name, value) in property_buffer.drain(..) {
194 let maybe_serialized_descriptor = if state.options.use_reflection() {
195 find_serialized_property_descriptor(
196 &instance.class,
197 property_name,
198 state.options.database,
199 )
200 } else {
201 None
202 };
203
204 if let Some(serialized_descriptor) = maybe_serialized_descriptor {
205 let data_type = match &serialized_descriptor.data_type {
206 DataType::Value(data_type) => *data_type,
207 DataType::Enum(_enum_name) => VariantType::Enum,
208 _ => unimplemented!(),
209 };
210
211 let mut serialized_name = serialized_descriptor.name.as_ref();
212
213 let mut converted_value = match value.try_convert_ref(instance.class, data_type) {
214 Ok(value) => value,
215 Err(message) => {
216 return Err(
217 writer.error(EncodeErrorKind::UnsupportedPropertyConversion {
218 class_name: instance.class.to_string(),
219 property_name: property_name.to_string(),
220 expected_type: data_type,
221 actual_type: value.ty(),
222 message,
223 }),
224 )
225 }
226 };
227
228 if let PropertyKind::Canonical {
230 serialization: PropertySerialization::Migrate(migration),
231 } = &serialized_descriptor.kind
232 {
233 if let Ok(new_value) = migration.perform(&converted_value) {
236 converted_value = Cow::Owned(new_value);
237 serialized_name = &migration.new_property_name
238 }
239 }
240
241 write_value_xml(writer, state, serialized_name, &converted_value)?;
242 } else {
243 match state.options.property_behavior {
244 EncodePropertyBehavior::IgnoreUnknown => {}
245 EncodePropertyBehavior::WriteUnknown | EncodePropertyBehavior::NoReflection => {
246 write_value_xml(writer, state, property_name, value)?;
250 }
251 EncodePropertyBehavior::ErrorOnUnknown => {
252 return Err(writer.error(EncodeErrorKind::UnknownProperty {
253 class_name: instance.class.to_string(),
254 property_name: property_name.to_string(),
255 }));
256 }
257 }
258 }
259 }
260
261 writer.write(XmlWriteEvent::end_element())?;
262
263 for child_id in instance.children() {
264 serialize_instance(writer, state, tree, *child_id, property_buffer)?;
265 }
266
267 writer.write(XmlWriteEvent::end_element())?;
268
269 Ok(())
270}
271
272fn serialize_shared_strings<W: Write>(
273 writer: &mut XmlEventWriter<W>,
274 state: &mut EmitState,
275) -> Result<(), NewEncodeError> {
276 if state.shared_strings_to_emit.is_empty() {
277 return Ok(());
278 }
279
280 writer.write(XmlWriteEvent::start_element("SharedStrings"))?;
281
282 for value in state.shared_strings_to_emit.values() {
283 let full_hash = value.hash();
286 let truncated_hash = &full_hash.as_bytes()[..16];
287
288 writer.write(
289 XmlWriteEvent::start_element("SharedString")
290 .attr("md5", &base64::encode(truncated_hash)),
291 )?;
292
293 writer.write_string(&base64::encode(value.data()))?;
294 writer.end_element()?;
295 }
296
297 writer.end_element()?;
298 Ok(())
299}