How to save parts, and the idea of Serialization

Looking great. Doesn’t seem to lag when loading nor saving. If you’re planning to make the world bigger there might be a need to optimize it size-wise, else you’re good. As for loading, if you wanna make that faster, perhaps just replace the generic for loop with a renderstep, maybe doing everything under a single for loop is enough to make it slightly faster.

@KreatorKols Unions are a hard thing to save unfortunately! The problem is if you wanted to save a union, you technically need to save the parts that make up the union (the negative parts or whatever), and currently there is no way to get those. So unless the unions are common object that you have presets for like in Czlopek’s case, I don’t think you can do much besides keeping a table for each union specifying details on the parts that make up the union, information used to save and load those parts back, and do the necessary operations (the :UnionAsync() method) to make the union again.

4 Likes

(This is my first post on the forms so sorry if its bad) I just wanted to start by saying this is really well worded! I do have a question though. I have been trying to serialize a RodConstraint and its attachments, I have gotten the attachments to work but I’ve gotten stuck on RodConstraints, specifically the “Attachment0” and “Attachment1” part of RodConstraints. Any ideas on how serialize it?

2 Likes

for me roblox should add byte arrays to roblox

1 Like

Not a terrible idea, you can create something like that on your own. One does already exist though, check this out

1 Like

Hey! Sorry for the late answer (and what are the odds, as soon as I noticed your reply you made a topic about the question).

I didn’t actually cover this, and I should to be honest! There are many ways to take care of properties set to instances, but I think the easiest way would be to include a separate dictionary inside of the saved table, where each key is an id, set to a serialized instance.

When you save an object which has a property set to an instance, say Attachment0, you will generate a random id, in the serialized form set the property to that id.
This id will serve as a lookup in the separate dictionary I mentioned. In this dictionary you will save the serialized form of the instance the property was set to. I think you know where this is going.

When deserializing, if you come across Attachment0, you will grab its stored id, look for the part corresponding to its id inside of that dictionary, deserialize the part, and set Attachment0 to the part!

1 Like

Alright, I have to admit I’m a little confused but hey its the only way we learn! My main question is how would I identify the id? I guess I don’t really understand the whole id thing.

1 Like

The serialized property contains the id. The id is randomly generated when serializing, and the instance the property was set to will be serialized and inserted into a dictionary (that is saved too) under a key, where that key is the randomly generated id.

When deserializing, when you deserialize the property, you grab the saved id, go to the saved table containing the ids and corresponding instances, and grab the instance corresponding to the id you have, and deserialize it, and set the property to the instance!

This is the processs in short:
Serialization:

  • Generate ID
  • Set serialized property to the ID
  • Serialize instance the property is set to
  • Inside of a saved dictionary containing ids, insert the serialized part under the generated id

Deserialization:

  • Grab the ID from saved property
  • Index the saved dictionary containing the IDs to get the serialized part
  • Deserialize
  • Set the property to the deserialized part

And one thing, if the instance the property is set to is a child of some object (or maybe the object indexing the property itself), it might be serialized twice, and two copies might exist when you deserialize. To circumvent this, you might need to add some checks, or maybe change how the ID is saved.

1 Like

Could you give a code example? I really want to learn how to do this, but I simply don’t think I’m going to be able to do it without some help. I also understand writing a code sample is typically more time consuming then just a normal reply so don’t feel any pressure to do so.

Hi! So I have a building game and I am making a save/load feature. I have followed your instructions and everything is working, expect whenever you make for example a small house and save it, make some changes and save it again, all the blocks that were already saved are saved again. So you will have two houses in one because all the blocks are saved twice. I tried to fix this by going through the database with a for loop and check all the positions of the already saved blocks. Whenever the position of the new block is already saved in the database, it shouldn’t save. But it does. I have been trying to fix this for the last two days without success. Maybe you can help me?

This is the code of the InitProps function (everything else is the same as in the topic):

function InitProps(objects)
	local DS = game:GetService("DataStoreService"):GetDataStore("Slot")
	local ency = DS:GetAsync(tostring(game.Players:FindFirstChildOfClass("Player").UserId))

	local decoded = HttpService:JSONDecode(ency)

	local tableToSave = {}

	for _, obj in pairs(objects) do
		local class = obj.ClassName
		local t = tableToSave[class]

		local alreadyExists = false 

		if not(t) then
			tableToSave[class] = {}
			t = tableToSave[class]
		end

		for _, others in pairs(decoded) do
			for prop, value in pairs(others) do
				for data,values in pairs(value) do
					if data == "Position" then
						local posx = obj.Position.X
						local posy = obj.Position.Y
						local posz = obj.Position.Z
						
						if posx ~= values.X or posy ~= values.Y or posz ~= values.Z then
							
						else
							print("NO")
							alreadyExists = true
						end
					end
				end
			end
		end
		
		print(alreadyExists)
		
		if not alreadyExists then
			local add = {}

			for _, Prop in pairs(Properties[obj.ClassName]) do
				add[Prop] = Serialize(obj[Prop])
			end

			local children = obj:GetChildren()

			if #children > 0 then
				add["Children"] = InitProps(children)
			end
			table.insert(t, add)
		end
	end
	return tableToSave
end
1 Like

Why don’t you instead of saving what was newly added, re-save the entire house again. Or are you looking for efficiency? In that case, perhaps a simpler solution is having an array of anything added after the last save. When saving again add it to the previous save, and empty this array for it to be refilled again.

Why don’t you instead of saving what was newly added, re-save the entire house again.

I tried to do this first, but I found on the Forum that you can’t delete any data from the Datastore for some reason, so I thought it was impossible.

1 Like

Well you don’t have to delete it, if you save the serialized data under the same it will overwrite the old data.

Could you please show me an example of what you mean?

A little overthinking on saving EnumItem, just save EnumItem.Value and it will return the number and it will still work. Some parts could have been done better, but we appreciate your contribution to the community anyway.

1 Like

I’m getting the error invalid argument #1 to 'pairs' (table expected, got nil)
my code is

local ourFolder = workspace.Places[player.Name]:GetChildren()

    for _, v in pairs(ourFolder) do
        print("got item")
    end

    local result, Err = pcall(function()
        DS:SetAsync(player.UserId, Save.Encrypt(ourFolder))
    end)

it does print “got item”

Weirdly enough though the Value does actually change overtime it seems! As it did here

image

3 Likes

What are the odds of my words turned against me. :joy:

Well, glad you pointed that out. I see why we should use strings instead of a number. I appreciate it.

3 Likes

I’m currently working on a module called Instance Service. Which can save every type of instance using DataStore2. Fully configurable.
Everything works well right now. It’ll come out 2 weeks later.

I’d wanted to say that here because many people actually really wants to learn how to save parts.

2 Likes

One suggestion that may help your saving system(when using cframes) is to neglect one direction vector, let’s say rightVector since it can be reconstructed using :Cross() function among with the other 2 direction vectors.
So instead of saving actual matrix values obtained by using cf:GetComponents() you are saving 25% less.
This doesn’t help right away, but in huge data sizes, it may be a great improvement!

Also… there could be a problem when trying to save any vector that have many decimal digits since they charge up the string when serializing. One way to avoid this is to trim the resulted string, which may result a loss in data accuracy, but it provides more saving space.

1 Like

Indeed! There are many strategies to compress the size of saved data. I should mention the possible need of this in the article.

As well, there are even better ways to save CFrames! Only with 2 vectors in fact instead of 3. The first vector would hold the position, the second vector would encode the rotation of the CFrame in axis-angle form (the rotation of the cframe that is expressed by what’s returned from :ToAxisAngle()), this form requires an axis (a unit vector) and a rotation along that axis (a number/scalar) to describe a rotation.

Since the axis is a unit vector, we can encode the rotation through the that unit’s vector length, and we’d still know what the unit vector is simply by doing a .Unit operation.

local axis, rotation = cf:ToAxisAngle()
local saved_rotation = axis*rotation

-- save saved_rotation

local axis, rotation = saved_rotation.Unit, saved_rotation.Magnitude
local CFrame = CFrame.fromAxisAngle(axis, rotation)

-- load saved_rotation

(we’re not saving the position in this example, but it’s just saving another vector)

1 Like