1use std::{
6 borrow::Cow,
7 collections::{HashMap, HashSet},
8};
9
10use rbx_dom_weak::{
11 types::{Ref, Variant, VariantType},
12 Ustr, UstrMap,
13};
14use serde::{Deserialize, Serialize};
15use strum::Display;
16
17use crate::{
18 session_id::SessionId,
19 snapshot::{
20 AppliedPatchSet, InstanceMetadata as RojoInstanceMetadata, InstanceWithMeta, RojoTree,
21 },
22};
23
24pub(crate) const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
26
27pub const PROTOCOL_VERSION: u64 = 5;
29
30#[derive(Debug, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct SubscribeMessage<'a> {
34 pub removed: Vec<Ref>,
35 pub added: HashMap<Ref, Instance<'a>>,
36 pub updated: Vec<InstanceUpdate>,
37}
38
39impl<'a> SubscribeMessage<'a> {
40 pub(crate) fn from_patch_update(tree: &'a RojoTree, patch: AppliedPatchSet) -> Self {
41 let removed = patch.removed;
42
43 let mut added = HashMap::new();
44 for id in patch.added {
45 let instance = tree.get_instance(id).unwrap();
46 added.insert(id, Instance::from_rojo_instance(instance));
47
48 for instance in tree.descendants(id) {
49 added.insert(instance.id(), Instance::from_rojo_instance(instance));
50 }
51 }
52
53 let updated = patch
54 .updated
55 .into_iter()
56 .map(|update| {
57 let changed_metadata = update
58 .changed_metadata
59 .as_ref()
60 .map(InstanceMetadata::from_rojo_metadata);
61
62 let changed_properties = update
63 .changed_properties
64 .into_iter()
65 .filter(|(_key, value)| property_filter(value.as_ref()))
66 .collect();
67
68 InstanceUpdate {
69 id: update.id,
70 changed_name: update.changed_name,
71 changed_class_name: update.changed_class_name,
72 changed_properties,
73 changed_metadata,
74 }
75 })
76 .collect();
77
78 Self {
79 removed,
80 added,
81 updated,
82 }
83 }
84}
85
86#[derive(Debug, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct InstanceUpdate {
89 pub id: Ref,
90 pub changed_name: Option<String>,
91 pub changed_class_name: Option<Ustr>,
92
93 #[serde(default)]
96 pub changed_properties: UstrMap<Option<Variant>>,
97 pub changed_metadata: Option<InstanceMetadata>,
98}
99
100#[derive(Debug, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct InstanceMetadata {
103 pub ignore_unknown_instances: bool,
104}
105
106impl InstanceMetadata {
107 pub(crate) fn from_rojo_metadata(meta: &RojoInstanceMetadata) -> Self {
108 Self {
109 ignore_unknown_instances: meta.ignore_unknown_instances,
110 }
111 }
112}
113
114#[derive(Debug, Serialize, Deserialize)]
115#[serde(rename_all = "PascalCase")]
116pub struct Instance<'a> {
117 pub id: Ref,
118 pub parent: Ref,
119 pub name: Cow<'a, str>,
120 pub class_name: Ustr,
121 pub properties: UstrMap<Cow<'a, Variant>>,
122 pub children: Cow<'a, [Ref]>,
123 pub metadata: Option<InstanceMetadata>,
124}
125
126impl Instance<'_> {
127 pub(crate) fn from_rojo_instance(source: InstanceWithMeta<'_>) -> Instance<'_> {
128 let properties = source
129 .properties()
130 .iter()
131 .filter(|(_key, value)| property_filter(Some(value)))
132 .map(|(key, value)| (*key, Cow::Borrowed(value)))
133 .collect();
134
135 Instance {
136 id: source.id(),
137 parent: source.parent(),
138 name: Cow::Borrowed(source.name()),
139 class_name: source.class_name(),
140 properties,
141 children: Cow::Borrowed(source.children()),
142 metadata: Some(InstanceMetadata::from_rojo_metadata(source.metadata())),
143 }
144 }
145}
146
147fn property_filter(value: Option<&Variant>) -> bool {
148 let ty = value.map(|value| value.ty());
149
150 ty != Some(VariantType::SharedString)
153}
154
155#[derive(Debug, Serialize, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct ServerInfoResponse {
159 pub session_id: SessionId,
160 pub server_version: String,
161 pub protocol_version: u64,
162 pub project_name: String,
163 pub expected_place_ids: Option<HashSet<u64>>,
164 pub unexpected_place_ids: Option<HashSet<u64>>,
165 pub game_id: Option<u64>,
166 pub place_id: Option<u64>,
167 pub root_instance_id: Ref,
168}
169
170#[derive(Debug, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct ReadResponse<'a> {
174 pub session_id: SessionId,
175 pub message_cursor: u32,
176 pub instances: HashMap<Ref, Instance<'a>>,
177}
178
179#[derive(Debug, Serialize, Deserialize)]
180#[serde(rename_all = "camelCase")]
181pub struct WriteRequest {
182 pub session_id: SessionId,
183 pub removed: Vec<Ref>,
184
185 #[serde(default)]
186 pub added: HashMap<Ref, ()>,
187 pub updated: Vec<InstanceUpdate>,
188}
189
190#[derive(Debug, Serialize, Deserialize)]
191#[serde(rename_all = "camelCase")]
192pub struct WriteResponse {
193 pub session_id: SessionId,
194}
195
196#[derive(Debug, Serialize, Deserialize, Display, Eq, PartialEq)]
198#[serde(rename_all = "camelCase")]
199#[strum(serialize_all = "camelCase")]
200pub enum SocketPacketType {
201 Messages,
202 }
206
207#[derive(Debug, Serialize, Deserialize)]
209#[serde(rename_all = "camelCase")]
210pub struct MessagesPacket<'a> {
211 pub message_cursor: u32,
212 pub messages: Vec<SubscribeMessage<'a>>,
213}
214
215#[derive(Debug, Serialize, Deserialize)]
217#[serde(untagged)]
218pub enum SocketPacketBody<'a> {
219 Messages(MessagesPacket<'a>),
220 }
224
225#[derive(Debug, Serialize, Deserialize)]
227#[serde(rename_all = "camelCase")]
228pub struct SocketPacket<'a> {
229 pub session_id: SessionId,
230 pub packet_type: SocketPacketType,
231 pub body: SocketPacketBody<'a>,
232}
233
234#[derive(Debug, Serialize, Deserialize)]
236#[serde(rename_all = "camelCase")]
237pub struct OpenResponse {
238 pub session_id: SessionId,
239}
240
241#[derive(Debug, Serialize, Deserialize)]
242#[serde(rename_all = "camelCase")]
243pub struct SerializeRequest {
244 pub session_id: SessionId,
245 pub ids: Vec<Ref>,
246}
247
248#[derive(Debug, Serialize, Deserialize)]
249#[serde(rename_all = "camelCase")]
250pub struct SerializeResponse {
251 pub session_id: SessionId,
252 #[serde(with = "serde_bytes")]
253 pub model_contents: Vec<u8>,
254}
255
256#[derive(Debug, Serialize, Deserialize)]
257#[serde(rename_all = "camelCase")]
258pub struct RefPatchRequest {
259 pub session_id: SessionId,
260 pub ids: HashSet<Ref>,
261}
262
263#[derive(Debug, Serialize, Deserialize)]
264#[serde(rename_all = "camelCase")]
265pub struct RefPatchResponse<'a> {
266 pub session_id: SessionId,
267 pub patch: SubscribeMessage<'a>,
268}
269
270#[derive(Debug, Serialize, Deserialize)]
272#[serde(rename_all = "camelCase")]
273pub struct ErrorResponse {
274 kind: ErrorResponseKind,
275 details: String,
276}
277
278impl ErrorResponse {
279 pub fn not_found<S: Into<String>>(details: S) -> Self {
280 Self {
281 kind: ErrorResponseKind::NotFound,
282 details: details.into(),
283 }
284 }
285
286 pub fn bad_request<S: Into<String>>(details: S) -> Self {
287 Self {
288 kind: ErrorResponseKind::BadRequest,
289 details: details.into(),
290 }
291 }
292
293 pub fn forbidden<S: Into<String>>(details: S) -> Self {
294 Self {
295 kind: ErrorResponseKind::Forbidden,
296 details: details.into(),
297 }
298 }
299
300 pub fn internal_error<S: Into<String>>(details: S) -> Self {
301 Self {
302 kind: ErrorResponseKind::InternalError,
303 details: details.into(),
304 }
305 }
306}
307
308#[derive(Debug, Serialize, Deserialize)]
309pub enum ErrorResponseKind {
310 NotFound,
311 BadRequest,
312 Forbidden,
313 InternalError,
314}