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};
15
16use crate::{
17 session_id::SessionId,
18 snapshot::{
19 AppliedPatchSet, InstanceMetadata as RojoInstanceMetadata, InstanceWithMeta, RojoTree,
20 },
21};
22
23pub(crate) const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
25
26pub const PROTOCOL_VERSION: u64 = 4;
28
29#[derive(Debug, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct SubscribeMessage<'a> {
33 pub removed: Vec<Ref>,
34 pub added: HashMap<Ref, Instance<'a>>,
35 pub updated: Vec<InstanceUpdate>,
36}
37
38impl<'a> SubscribeMessage<'a> {
39 pub(crate) fn from_patch_update(tree: &'a RojoTree, patch: AppliedPatchSet) -> Self {
40 let removed = patch.removed;
41
42 let mut added = HashMap::new();
43 for id in patch.added {
44 let instance = tree.get_instance(id).unwrap();
45 added.insert(id, Instance::from_rojo_instance(instance));
46
47 for instance in tree.descendants(id) {
48 added.insert(instance.id(), Instance::from_rojo_instance(instance));
49 }
50 }
51
52 let updated = patch
53 .updated
54 .into_iter()
55 .map(|update| {
56 let changed_metadata = update
57 .changed_metadata
58 .as_ref()
59 .map(InstanceMetadata::from_rojo_metadata);
60
61 let changed_properties = update
62 .changed_properties
63 .into_iter()
64 .filter(|(_key, value)| property_filter(value.as_ref()))
65 .collect();
66
67 InstanceUpdate {
68 id: update.id,
69 changed_name: update.changed_name,
70 changed_class_name: update.changed_class_name,
71 changed_properties,
72 changed_metadata,
73 }
74 })
75 .collect();
76
77 Self {
78 removed,
79 added,
80 updated,
81 }
82 }
83}
84
85#[derive(Debug, Serialize, Deserialize)]
86#[serde(rename_all = "camelCase")]
87pub struct InstanceUpdate {
88 pub id: Ref,
89 pub changed_name: Option<String>,
90 pub changed_class_name: Option<Ustr>,
91
92 #[serde(default)]
95 pub changed_properties: UstrMap<Option<Variant>>,
96 pub changed_metadata: Option<InstanceMetadata>,
97}
98
99#[derive(Debug, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub struct InstanceMetadata {
102 pub ignore_unknown_instances: bool,
103}
104
105impl InstanceMetadata {
106 pub(crate) fn from_rojo_metadata(meta: &RojoInstanceMetadata) -> Self {
107 Self {
108 ignore_unknown_instances: meta.ignore_unknown_instances,
109 }
110 }
111}
112
113#[derive(Debug, Serialize, Deserialize)]
114#[serde(rename_all = "PascalCase")]
115pub struct Instance<'a> {
116 pub id: Ref,
117 pub parent: Ref,
118 pub name: Cow<'a, str>,
119 pub class_name: Ustr,
120 pub properties: UstrMap<Cow<'a, Variant>>,
121 pub children: Cow<'a, [Ref]>,
122 pub metadata: Option<InstanceMetadata>,
123}
124
125impl Instance<'_> {
126 pub(crate) fn from_rojo_instance(source: InstanceWithMeta<'_>) -> Instance<'_> {
127 let properties = source
128 .properties()
129 .iter()
130 .filter(|(_key, value)| property_filter(Some(value)))
131 .map(|(key, value)| (*key, Cow::Borrowed(value)))
132 .collect();
133
134 Instance {
135 id: source.id(),
136 parent: source.parent(),
137 name: Cow::Borrowed(source.name()),
138 class_name: source.class_name(),
139 properties,
140 children: Cow::Borrowed(source.children()),
141 metadata: Some(InstanceMetadata::from_rojo_metadata(source.metadata())),
142 }
143 }
144}
145
146fn property_filter(value: Option<&Variant>) -> bool {
147 let ty = value.map(|value| value.ty());
148
149 ty != Some(VariantType::SharedString)
152}
153
154#[derive(Debug, Serialize, Deserialize)]
156#[serde(rename_all = "camelCase")]
157pub struct ServerInfoResponse {
158 pub session_id: SessionId,
159 pub server_version: String,
160 pub protocol_version: u64,
161 pub project_name: String,
162 pub expected_place_ids: Option<HashSet<u64>>,
163 pub unexpected_place_ids: Option<HashSet<u64>>,
164 pub game_id: Option<u64>,
165 pub place_id: Option<u64>,
166 pub root_instance_id: Ref,
167}
168
169#[derive(Debug, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct ReadResponse<'a> {
173 pub session_id: SessionId,
174 pub message_cursor: u32,
175 pub instances: HashMap<Ref, Instance<'a>>,
176}
177
178#[derive(Debug, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct WriteRequest {
181 pub session_id: SessionId,
182 pub removed: Vec<Ref>,
183
184 #[serde(default)]
185 pub added: HashMap<Ref, ()>,
186 pub updated: Vec<InstanceUpdate>,
187}
188
189#[derive(Debug, Serialize, Deserialize)]
190#[serde(rename_all = "camelCase")]
191pub struct WriteResponse {
192 pub session_id: SessionId,
193}
194
195#[derive(Debug, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct SubscribeResponse<'a> {
199 pub session_id: SessionId,
200 pub message_cursor: u32,
201 pub messages: Vec<SubscribeMessage<'a>>,
202}
203
204#[derive(Debug, Serialize, Deserialize)]
206#[serde(rename_all = "camelCase")]
207pub struct OpenResponse {
208 pub session_id: SessionId,
209}
210
211#[derive(Debug, Serialize, Deserialize)]
213#[serde(rename_all = "camelCase")]
214pub struct ErrorResponse {
215 kind: ErrorResponseKind,
216 details: String,
217}
218
219impl ErrorResponse {
220 pub fn not_found<S: Into<String>>(details: S) -> Self {
221 Self {
222 kind: ErrorResponseKind::NotFound,
223 details: details.into(),
224 }
225 }
226
227 pub fn bad_request<S: Into<String>>(details: S) -> Self {
228 Self {
229 kind: ErrorResponseKind::BadRequest,
230 details: details.into(),
231 }
232 }
233
234 pub fn internal_error<S: Into<String>>(details: S) -> Self {
235 Self {
236 kind: ErrorResponseKind::InternalError,
237 details: details.into(),
238 }
239 }
240}
241
242#[derive(Debug, Serialize, Deserialize)]
243pub enum ErrorResponseKind {
244 NotFound,
245 BadRequest,
246 InternalError,
247}