├── .gitattributes ├── LICENSE ├── README.md └── rose.luau /.gitattributes: -------------------------------------------------------------------------------- 1 | *.luau linguist-language=lua -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Gem_API 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rose 2 | 3 | ![GitHub release (with filter)](https://img.shields.io/github/v/release/Gem-API/Rose) 4 | 5 | 6 | Rose, short for Roblox Serializer (RoSe), is a feature-complete Roblox Instance serializer, written entirely in Luau. It is capable of converting any instantiable Roblox instance into a corresponding Lua table while preserving children recursively and storing any writable properties. 7 | 8 | ## Key Objectives 9 | 10 | Rose is designed with three primary objectives: 11 | 12 | 1. **General Purpose**: Rose aims to provide a versatile solution that can handle anything from Model to GUI serialization. 13 | 14 | 2. **Space Efficiency**: Serialized tables should be as small as possible. 15 | 16 | 3. **Speed**: Rose is designed for efficiency and speed, even when dealing with complex Roblox data structures, with speeds of around 120,000 instances/second serialization and 70,000 instance/second deserialization. 17 | 18 | ## What Rose is Not 19 | 20 | It's important to note that Rose is not a complete Roblox-to-Data solution. Instead, it serves as a critical component (the "missing link") within a broader data serialization workflow. An example workflow is as follows: 21 | 22 | 1. Convert a Roblox Instance to a Lua table using Rose. 23 | 2. Convert the Lua table to a language-agnostic format like JSON or Messagepack. 24 | 3. Compress the language-agnostic format (Rose, while efficient in size, inevitably contains repetitive data. This makes compression highly effective, often reducing size by 80%). 25 | 4. Encode the compressed data for storage on platforms that do not support binary data storage. 26 | 27 | ## Contributing Guide 28 | 29 | Contributors to the Rose project should adhere to the following guidelines: 30 | 31 | - **Versioning**: Rose uses [Simple Versioning](https://simver.org/) as its versioning scheme. Major versions indicate breaking changes to the specification, ensuring compatibility with tables created by previous Rose versions. 32 | 33 | - **Code Style**: Maintain the code style format of the project. Keep line lengths at 80 characters or less. 34 | 35 | ### Updating the dataset 36 | 37 | Rose uses a predefined lookup table to determine what it can and cannot serialize. The most basic (but valuable) contribution is updating the dataset with new Instance types or properties. 38 | 39 | To update the dataset, add another item in the `MUTABLE_PROPERTIES` variable with the following format: 40 | 41 | ```lua 42 | ClassNameOfInstance = { 43 | "Property1", 44 | "Property2", 45 | 46 | "Property3", 47 | }, 48 | ``` 49 | 50 | Order properties based on their sequence in the Roblox Studio properties window and leave an empty line between distinct property categories as displayed in the properties panel. 51 | 52 | Some properties cannot be written to, only read from. Do not put read-only properties in this panel because the script will fail when setting them during deserialization. The [Roblox API](https://create.roblox.com/docs/en-us/reference/engine) is your friend. 53 | 54 | The `Parent` property of all Instances is handled automatically, so please do not include it. 55 | 56 | Before submitting a pull request, please test your new dataset by serializing and deserializing any classes you have added or changed, making sure that every property you intended to be saved has been saved and loaded correctly. -------------------------------------------------------------------------------- /rose.luau: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- -- 3 | -- `7MM"""Mq. .M"""bgd -- 4 | -- MM `MM. ,MI "Y -- 5 | -- MM ,M9 ,pW"Wq. `MMb. .gP"Ya -- 6 | -- MMmmdM9 6W' `Wb `YMMNq. ,M' Yb -- 7 | -- MM YM. 8M M8 . `MM 8M"""""" -- 8 | -- MM `Mb.YA. ,A9 Mb dM YM. , -- 9 | -- .JMML. .JMM.`Ybmd9' P"Ybmmd" `Mbmmd' -- 10 | -- -- 11 | -- Roblox Instance Serializer -- 12 | -- -- 13 | -------------------------------------------------------------------------------- 14 | -- -- 15 | -- Copyright (c) Gem_API -- 16 | -- -- 17 | -- Permission is hereby granted, free of charge, to any person obtaining -- 18 | -- a copy of this software and associated documentation files (the -- 19 | -- "Software"), to deal in the Software without restriction, including -- 20 | -- without limitation the rights to use, copy, modify, merge, publish, -- 21 | -- distribute, sublicense, and/or sell copies of the Software, and to -- 22 | -- permit persons to whom the Software is furnished to do so, subject to -- 23 | -- the following conditions: -- 24 | -- -- 25 | -- The above copyright notice and this permission notice shall be -- 26 | -- included in all copies or substantial portions of the Software. -- 27 | -- -- 28 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -- 29 | -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -- 30 | -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -- 31 | -- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -- 32 | -- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -- 33 | -- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -- 34 | -- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -- 35 | -- -- 36 | -------------------------------------------------------------------------------- 37 | -- Utilities 38 | 39 | local function invert_table(t) 40 | local s = {} 41 | for k in t do 42 | s[t[k]] = k 43 | end 44 | return s 45 | end 46 | 47 | local table_pack = table.pack 48 | local table_unpack = table.unpack 49 | 50 | local Instance_new = Instance.new 51 | local Vector2_new = Vector2.new 52 | local Vector3_new = Vector3.new 53 | local Color3_new = Color3.new 54 | local ColorSequence_new = ColorSequence.new 55 | local ColorSequenceKeypoint_new = ColorSequenceKeypoint.new 56 | local CFrame_new = CFrame.new 57 | local NumberRange_new = NumberRange.new 58 | local NumberSequenceKeypoint_new = NumberSequenceKeypoint.new 59 | local NumberSequence_new = NumberSequence.new 60 | 61 | -------------------------------------------------------------------------------- 62 | -- Datasets 63 | 64 | local ROSE_VERSION = 2 65 | 66 | local DESERIALIZE_DEFAULT_PARENT = game:GetService("Lighting") 67 | 68 | local MUTABLE_PROPERTIES = { 69 | Folder = { 70 | "Name" 71 | }, 72 | Model = { 73 | "Name", 74 | 75 | "PrimaryPart", 76 | 77 | "WorldPivot" 78 | }, 79 | SpecialMesh = { 80 | "MeshId", 81 | "MeshType", 82 | "Name", 83 | "Offset", 84 | "Scale", 85 | "TextureId" 86 | }, 87 | BlockMesh = { 88 | "Name", 89 | "Offset", 90 | "Scale", 91 | "VertexColor" 92 | }, 93 | CylinderMesh = { 94 | "Name", 95 | "Offset", 96 | "Scale", 97 | "VertexColor" 98 | }, 99 | Weld = { 100 | "C0", 101 | "C1", 102 | "Name", 103 | "Part0", 104 | "Part1", 105 | 106 | "Enabled" 107 | }, 108 | Snap = { 109 | "C0", 110 | "C1", 111 | "Name", 112 | "Part0", 113 | "Part1", 114 | 115 | "Enabled" 116 | }, 117 | Part = { 118 | "CastShadow", 119 | "Color", 120 | "Material", 121 | "Reflectance", 122 | "Transparency", 123 | 124 | "Locked", 125 | "Name", 126 | 127 | "Size", 128 | "Position", 129 | "Rotation", 130 | 131 | "CanCollide", 132 | "Anchored", 133 | "Shape", 134 | 135 | "BackSurface", 136 | "BottomSurface", 137 | "FrontSurface", 138 | "LeftSurface", 139 | "RightSurface", 140 | "TopSurface" 141 | }, 142 | WedgePart = { 143 | "CastShadow", 144 | "Color", 145 | "Material", 146 | "Reflectance", 147 | "Transparency", 148 | 149 | "Locked", 150 | "Name", 151 | 152 | "Size", 153 | "Position", 154 | "Rotation", 155 | 156 | "CanCollide", 157 | "Anchored", 158 | 159 | "BackSurface", 160 | "BottomSurface", 161 | "FrontSurface", 162 | "LeftSurface", 163 | "RightSurface", 164 | "TopSurface" 165 | }, 166 | CornerWedgePart = { 167 | "CastShadow", 168 | "Color", 169 | "Material", 170 | "Reflectance", 171 | "Transparency", 172 | 173 | "Locked", 174 | "Name", 175 | 176 | "Size", 177 | "Position", 178 | "Rotation", 179 | 180 | "CanCollide", 181 | "Anchored", 182 | 183 | "BackSurface", 184 | "BottomSurface", 185 | "FrontSurface", 186 | "LeftSurface", 187 | "RightSurface", 188 | "TopSurface" 189 | }, 190 | TrussPart = { 191 | "CastShadow", 192 | "Color", 193 | "Material", 194 | "Reflectance", 195 | "Transparency", 196 | 197 | "Locked", 198 | "Name", 199 | 200 | "Size", 201 | "Position", 202 | "Rotation", 203 | 204 | "CanCollide", 205 | "Anchored", 206 | "Shape", 207 | 208 | "BackSurface", 209 | "BottomSurface", 210 | "FrontSurface", 211 | "LeftSurface", 212 | "RightSurface", 213 | "TopSurface" 214 | }, 215 | SpawnLocation = { 216 | "CastShadow", 217 | "Color", 218 | "Material", 219 | "Reflectance", 220 | "Transparency", 221 | 222 | "Locked", 223 | "Name", 224 | 225 | "Size", 226 | "Position", 227 | "Rotation", 228 | 229 | "CanCollide", 230 | "Anchored", 231 | "Shape", 232 | 233 | "BackSurface", 234 | "BottomSurface", 235 | "FrontSurface", 236 | "LeftSurface", 237 | "RightSurface", 238 | "TopSurface", 239 | 240 | "Duration", 241 | 242 | "AllowTeamChangeOnTouch", 243 | "Neutral", 244 | "TeamColor" 245 | }, 246 | Seat = { 247 | "CastShadow", 248 | "Color", 249 | "Material", 250 | "Reflectance", 251 | "Transparency", 252 | 253 | "Locked", 254 | "Name", 255 | 256 | "Size", 257 | "Position", 258 | "Rotation", 259 | 260 | "CanCollide", 261 | "Anchored", 262 | "Shape", 263 | 264 | "BackSurface", 265 | "BottomSurface", 266 | "FrontSurface", 267 | "LeftSurface", 268 | "RightSurface", 269 | "TopSurface", 270 | 271 | "Disabled" 272 | }, 273 | VehicleSeat = { 274 | "CastShadow", 275 | "Color", 276 | "Material", 277 | "Reflectance", 278 | "Transparency", 279 | 280 | "Locked", 281 | "Name", 282 | 283 | "Size", 284 | "Position", 285 | "Rotation", 286 | 287 | "CanCollide", 288 | "Anchored", 289 | "Shape", 290 | 291 | "BackSurface", 292 | "BottomSurface", 293 | "FrontSurface", 294 | "LeftSurface", 295 | "RightSurface", 296 | "TopSurface", 297 | 298 | "Disabled", 299 | "HeadsUpDisplay", 300 | "MaxSpeed", 301 | "Steer", 302 | "SteerFloat", 303 | "Throttle", 304 | "ThrottleFloat", 305 | "Torque", 306 | "TurnSpeed" 307 | }, 308 | Decal = { 309 | "Color3", 310 | "Texture", 311 | "Transparency", 312 | "ZIndex", 313 | 314 | "Face", 315 | "Name" 316 | }, 317 | Texture = { 318 | "Color3", 319 | "OffsetStudsU", 320 | "OffsetStudsV", 321 | "StudsPerTileU", 322 | "StudsPerTileV", 323 | "Texture", 324 | "Transparency", 325 | "ZIndex", 326 | 327 | "Face", 328 | "Name" 329 | }, 330 | ClickDetector = { 331 | "MaxActivationDistance", 332 | "Name", 333 | 334 | "CursorIcon" 335 | }, 336 | PointLight = { 337 | "Brightness", 338 | "Color", 339 | "Enabled", 340 | "Range", 341 | "Shadows", 342 | 343 | "Name" 344 | }, 345 | SpotLight = { 346 | "Angle", 347 | "Brightness", 348 | "Color", 349 | "Enabled", 350 | "Face", 351 | "Range", 352 | "Shadows", 353 | 354 | "Name" 355 | }, 356 | SurfaceLight = { 357 | "Angle", 358 | "Brightness", 359 | "Color", 360 | "Enabled", 361 | "Face", 362 | "Range", 363 | "Shadows", 364 | 365 | "Name" 366 | }, 367 | Fire = { 368 | "Color", 369 | "Enabled", 370 | "Heat", 371 | "Name", 372 | "SecondaryColor", 373 | "Size", 374 | "TimeScale" 375 | }, 376 | Smoke = { 377 | "Color", 378 | "Enabled", 379 | "Name", 380 | "Opacity", 381 | "RiseVelocity", 382 | "Size", 383 | "TimeScale" 384 | }, 385 | Sparkles = { 386 | "Enabled", 387 | "Name", 388 | "SparkleColor", 389 | "TimeScale" 390 | }, 391 | ParticleEmitter = { 392 | "Color", 393 | "LightEmission", 394 | "LightInfluence", 395 | "Orientation", 396 | "Size", 397 | "Squash", 398 | "Texture", 399 | "Transparency", 400 | "ZOffset", 401 | 402 | "Name", 403 | "EmissionDirection", 404 | "Enabled", 405 | "Lifetime", 406 | "Rate", 407 | "Rotation", 408 | "RotSpeed", 409 | "Speed", 410 | "SpreadAngle", 411 | 412 | "Shape", 413 | "ShapeInOut", 414 | "ShapeStyle", 415 | 416 | "FlipbookLayout", 417 | 418 | "Acceleration", 419 | 420 | "Drag", 421 | "LockedToPart", 422 | "TimeScale", 423 | "VelocityInheritance", 424 | "WindAffectsDrag" 425 | }, 426 | Beam = { 427 | "Color", 428 | "Enabled", 429 | "LightEmission", 430 | "LightInfluence", 431 | "Texture", 432 | "TextureLength", 433 | "TextureMode", 434 | "TextureSpeed", 435 | "Transparency", 436 | "ZOffset", 437 | 438 | "Name", 439 | 440 | "Attachment0", 441 | "Attachment1", 442 | "CurveSize0", 443 | "CurveSize1", 444 | "FaceCamera", 445 | "Segments", 446 | "Width0", 447 | "Width1" 448 | }, 449 | Attachment = { 450 | "Visible", 451 | 452 | "Name", 453 | 454 | "CFrame" 455 | }, 456 | } 457 | 458 | local IV_CLASSNAMES = {} 459 | 460 | for i in MUTABLE_PROPERTIES do 461 | IV_CLASSNAMES[#IV_CLASSNAMES + 1] = i 462 | end 463 | 464 | local VI_CLASSNAMES = invert_table(IV_CLASSNAMES) 465 | 466 | local IV_PROPERTY_TYPES = { 467 | "string", 468 | "number", 469 | "boolean", 470 | 471 | "NumberRange", 472 | "NumberSequence", 473 | 474 | "Vector2", 475 | "Vector3", 476 | 477 | "Color3", 478 | "ColorSequence", 479 | 480 | "EnumItem", 481 | 482 | "CFrame", 483 | 484 | "Instance" 485 | } 486 | 487 | local VI_PROPERTY_TYPES = invert_table(IV_PROPERTY_TYPES) 488 | 489 | local DEFAULT_INSTANCES = {} 490 | 491 | for className in MUTABLE_PROPERTIES do 492 | DEFAULT_INSTANCES[className] = Instance_new(className) 493 | end 494 | 495 | -------------------------------------------------------------------------------- 496 | -- Internal API 497 | 498 | local give_warnings = false 499 | 500 | local scope = nil 501 | 502 | local uid_counter = 0 503 | local uid_reference = {} 504 | 505 | local skipped_instances = 0 506 | local skipped_instance_types = {} 507 | 508 | local post_deserialize_links_counter = 0 509 | local post_deserialize_links = {} 510 | 511 | local function reset_internal_variables() 512 | scope = nil 513 | 514 | uid_counter = 0 515 | uid_reference = {} 516 | 517 | skipped_instances = 0 518 | skipped_instance_types = {} 519 | 520 | post_deserialize_links_counter = 0 521 | post_deserialize_links = {} 522 | end 523 | 524 | local function build_uid_reference(instance) 525 | uid_counter += 1 526 | uid_reference[instance] = uid_counter 527 | 528 | local children = instance:GetChildren() 529 | for i in children do 530 | build_uid_reference(children[i]) 531 | end 532 | end 533 | 534 | ---------------------------------------- 535 | -- Serializer 536 | 537 | local function serialize_property(p) 538 | local _type = typeof(p) 539 | 540 | local prop_type = VI_PROPERTY_TYPES[_type] 541 | if not prop_type then 542 | return nil 543 | end 544 | 545 | local value = nil 546 | 547 | if _type == "EnumItem" then 548 | value = p.Value 549 | 550 | elseif _type == "Vector3" then 551 | value = {p.X, p.Y, p.Z} 552 | 553 | elseif _type == "boolean" then 554 | value = p 555 | 556 | elseif _type == "Color3" then 557 | value = {p.R, p.G, p.B} 558 | 559 | elseif _type == "number" then 560 | value = p 561 | 562 | elseif _type == "string" then 563 | value = p 564 | 565 | elseif _type == "CFrame" then 566 | value = table_pack(p:GetComponents()) 567 | 568 | elseif _type == "Instance" then 569 | if not p:IsDescendantOf(scope) then 570 | warn("Object-type property references '" .. p.Name .. 571 | "' which is not in the serialization scope." 572 | ) 573 | 574 | return nil 575 | else 576 | value = uid_reference[p] 577 | end 578 | 579 | elseif _type == "Vector2" then 580 | value = {p.X, p.Y} 581 | 582 | elseif _type == "ColorSequence" then 583 | value = {} 584 | 585 | local kps = p.Keypoints 586 | for i in kps do 587 | local kp = kps[i] 588 | local kpc = kp.Value 589 | 590 | value[i] = {kp.Time, kpc.R, kpc.G, kpc.B} 591 | end 592 | 593 | elseif _type == "NumberRange" then 594 | value = {p.Min, p.Max} 595 | 596 | elseif _type == "NumberSequence" then 597 | value = {} 598 | 599 | local kps = p.Keypoints 600 | for i in kps do 601 | local kp = kps[i] 602 | 603 | value[i] = {kp.Time, kp.Value, kp.Envelope} 604 | end 605 | 606 | end 607 | 608 | return { 609 | prop_type, 610 | value 611 | } 612 | end 613 | 614 | local function serialize_instance(obj) 615 | local class_name = obj.ClassName 616 | 617 | local prop_pool = MUTABLE_PROPERTIES[class_name] 618 | 619 | local props = {} 620 | 621 | for i in prop_pool do 622 | local p = prop_pool[i] 623 | if DEFAULT_INSTANCES[class_name][p] == obj[p] then 624 | continue 625 | end 626 | props[p] = serialize_property(obj[p]) 627 | end 628 | 629 | local children_counter = 0 630 | local children = {} 631 | 632 | local child_pool = obj:GetChildren() 633 | for i in child_pool do 634 | local c = child_pool[i] 635 | if not MUTABLE_PROPERTIES[c.ClassName] then 636 | skipped_instance_types[c.ClassName] = 1 637 | skipped_instances += 1 638 | 639 | continue 640 | end 641 | children_counter += 1 642 | children[children_counter] = serialize_instance(c) 643 | end 644 | 645 | return { 646 | uid_reference[obj], 647 | VI_CLASSNAMES[class_name], 648 | props, 649 | children 650 | } 651 | end 652 | 653 | ---------------------------------------- 654 | -- Deserializer 655 | 656 | local function post_deserialize_link_pass() 657 | for i in post_deserialize_links do 658 | local v = post_deserialize_links[i] 659 | v[1][v[2]] = uid_reference[v[3]] 660 | end 661 | end 662 | 663 | 664 | local function deserialize_property(property, prop_name, instance) 665 | local prop_type = IV_PROPERTY_TYPES[property[1]] 666 | local value = property[2] 667 | 668 | local result = nil 669 | 670 | if prop_type == "EnumItem" then 671 | result = value 672 | 673 | elseif prop_type == "Vector3" then 674 | result = Vector3_new(value[1], value[2], value[3]) 675 | 676 | elseif prop_type == "boolean" then 677 | result = value 678 | 679 | elseif prop_type == "Color3" then 680 | result = Color3_new(value[1], value[2], value[3]) 681 | 682 | elseif prop_type == "number" then 683 | result = value 684 | 685 | elseif prop_type == "string" then 686 | result = value 687 | 688 | elseif prop_type == "CFrame" then 689 | result = CFrame_new(table_unpack(value)) 690 | 691 | elseif prop_type == "Instance" then 692 | post_deserialize_links_counter += 1 693 | post_deserialize_links[post_deserialize_links_counter] = 694 | {instance, prop_name, value} 695 | 696 | elseif prop_type == "Vector2" then 697 | result = Vector2_new(value.X, value.Y) 698 | 699 | elseif prop_type == "ColorSequence" then 700 | local kps = {} 701 | 702 | for i in value do 703 | local v = value[i] 704 | kps[i] = ColorSequenceKeypoint_new( 705 | v[1], Color3_new(v[2], v[3], v[4]) 706 | ) 707 | end 708 | 709 | result = ColorSequence_new(kps) 710 | 711 | elseif prop_type == "NumberRange" then 712 | result = NumberRange_new(value[1], value[2]) 713 | 714 | elseif prop_type == "NumberSequence" then 715 | local kps = {} 716 | 717 | for i in value do 718 | local v = value[i] 719 | kps[i] = NumberSequenceKeypoint_new( 720 | v[1], v[2], v[3] 721 | ) 722 | end 723 | 724 | result = NumberSequence_new(kps) 725 | 726 | end 727 | 728 | return result 729 | end 730 | 731 | local function deserialize_instance(obj, parent) 732 | local instance = Instance_new(IV_CLASSNAMES[obj[2]]) 733 | 734 | for i in obj[4] do 735 | deserialize_instance(obj[4][i], instance) 736 | end 737 | 738 | for name in obj[3] do 739 | instance[name] = deserialize_property(obj[3][name], name, instance) 740 | end 741 | 742 | uid_reference[obj[1]] = instance 743 | 744 | instance.Parent = parent or DESERIALIZE_DEFAULT_PARENT 745 | 746 | return instance 747 | end 748 | 749 | -------------------------------------------------------------------------------- 750 | -- Exposed API 751 | 752 | local rose = {} 753 | 754 | function rose.serialize(instance) 755 | if not MUTABLE_PROPERTIES[instance.ClassName] then 756 | warn("Given instance was not serializable") 757 | return nil 758 | end 759 | 760 | reset_internal_variables() 761 | 762 | build_uid_reference(instance) 763 | 764 | local result = serialize_instance(instance) 765 | 766 | if skipped_instances ~= 0 and give_warnings then 767 | warn("ROSE - Some instance(s) were not serialized:") 768 | for i in skipped_instance_types do 769 | warn(" Type '" .. i .. "'") 770 | end 771 | end 772 | 773 | return { 774 | ["version"] = ROSE_VERSION, 775 | ["tree"] = result 776 | } 777 | end 778 | 779 | function rose.deserialize(packet) 780 | if packet.version ~= ROSE_VERSION then 781 | warn("ROSE packet provided was generated by an older version of ROSE.") 782 | return nil 783 | end 784 | 785 | reset_internal_variables() 786 | 787 | local result = deserialize_instance(packet.tree) 788 | 789 | post_deserialize_link_pass() 790 | 791 | return result 792 | end 793 | 794 | function rose.give_warnings(value: boolean) 795 | give_warnings = value 796 | end 797 | 798 | return rose --------------------------------------------------------------------------------