Cookie (client-sided data management)!

Link: Model
Download: Download (5.1 KB)

Summary (v1.0.0)


“You’re relying on the CLIENT to store data?”
Don’t worry, I’ll explain!

This module does the opposite of what you’d expect with data storage systems. Typically, the server gets, changes, and saves a player’s data. With this module, the server lets the client change the data while the server still saves it. To prevent misuse, the module provides no methods to the server side to get the user’s data. Only the client can manage it.

Now it’s easy to save information that only the local client would need to know.
Here’s some examples:

  • Local notes on an item, promocode, map, or person
  • A drawing the user hasn’t published yet
  • Trade history
  • Easter eggs found
  • Check if tutorial is completed already
  • Check if particular tutorial hints have been shown already
  • New user checks

Remember, if anybody other than the local player or the server needs this information, this module would not be used correctly. You should instead link this with your own data system!

Exploit Concern


The obvious worry is exploits. What can exploiters do if this system is used?
Exploiters will have full control over this data. But, they also had full control over the entire client anyways, which is the only thing the data is actually used on… essentially giving them no extra power!

The only thing they could do is cause the datastore to be filled, causing slight lag when loading and saving. However, measures have been made to reduce this. Their data cannot exceed 75% of the character limit in their Datastore JSON. Encoding or decoding this JSON takes 25 ms max on average, assuming the maximum number of characters allowed using this system. However, normal usage should be even quicker than this!

Example Usage


For all of these examples, do the following:

  • Add a folder called ‘Modules’ in game.ReplicatedStorage
  • Place the Cookie module inside the folder
  • Add the following script to ServerScriptService:
require(game.ReplicatedStorage:WaitForChild("Modules").Cookie)

1: User’s unpublished drawings saved

Summary

SaveDrawing.rbxm (15.6 KB)

Here’s a preview of the client code:

local buttons = script.Parent.Buttons
local label = script.Parent.Label

local RunService = game:GetService("RunService")
local CaptureService = game:GetService("CaptureService")
local UserInputService = game:GetService("UserInputService")

local scale = label.UIScale.Scale
local size = label.AbsoluteSize / scale

local image = Instance.new("EditableImage")
image.Size = size

local offset = game.GuiService:GetGuiInset()

local radius = 5

--\\ Private Methods

local function getColor(): Color3
	if UserInputService:IsKeyDown(Enum.KeyCode.LeftAlt) then
		return Color3.new(1,1,1)
	else
		return Color3.new()
	end
end

local function update(cookie: Cookie.Cookie)
	cookie:Set(image:ReadPixels(Vector2.zero, size))
end

--\\ Modules

local Modules = game.ReplicatedStorage:WaitForChild("Modules")

local Cookie = require(Modules.Cookie)

--\\ Connections

local cookie = Cookie.new(`Drawing {size.X}x{size.Y}`, false)
local current = cookie:Get()

if current then
	image:WritePixels(Vector2.zero, size, current)
else
	image:DrawRectangle(Vector2.zero, size, Color3.new(1,1,1), 0)
end

image.Parent = label

update(cookie)
buttons.Clear.MouseButton1Click:Connect(function()
	image:DrawRectangle(Vector2.zero, size, Color3.new(1,1,1), 0)
	update(cookie)
end)

do
	local down = false
	local last = nil
	label.MouseButton1Down:Connect(function(x, y)
		down = true
		last = (Vector2.new(x, y) - label.AbsolutePosition - offset) / scale
		image:DrawCircle(last, radius, getColor(), 0)
	end)

	label.MouseMoved:Connect(function(x, y)
		if last and down then
			local now = (Vector2.new(x, y) - label.AbsolutePosition - offset) / scale

			local iter = 10
			for i = 1, iter do
				image:DrawCircle(last:Lerp(now, i / iter), radius, getColor(), 0)
			end

			last = now
		end
	end)

	label.MouseButton1Up:Connect(function()
		if last then
			last = nil
			down = false
			update(cookie)
		end
	end)

	label.MouseLeave:Connect(function()
		if last then
			last = nil
			down = false
			update(cookie)
		end
	end)
end

And the result!

2: Note-to-self

Summary

Download (5.1 KB)

Here’s a preview of the client code:

local input = script.Parent.Input

local RunService = game:GetService("RunService")

--\\ Modules

local Modules = game.ReplicatedStorage:WaitForChild("Modules")

local Cookie = require(Modules.Cookie)

--\\ Connections

local cookie = Cookie.new("Note", "Hello world!")

cookie:SetReliable(false)

input.Text = cookie:Get()

RunService.RenderStepped:Connect(function()
	cookie:Set(input.Text)
end)

And the result!

This type of functionality is used in Discord when you write something down in a channel without publishing the message yet!

API


local Super = require(path.to.module)
local cookie = Super.new("API", true)

Super.new(id: string, default: any?): Cookie

This constructor creates a new cookie.
It may yield if the module is still retrieving data from the server.

id: The name of the cookie
default: The value to be used if the cookie’s value is nil

(:yellow_circle: YIELDING ) ( :large_blue_diamond: CLIENT )

Super:GetAll(): {Cookie}

Returns all the active cookies created by the client.
It will not return cookies that were previously saved, but have not been created by the constructor yet.

( :large_blue_diamond: CLIENT )

Super:ClearAll()

Deletes cookie data on the client and server.
Deleted cookies will continue to function, but they will stop being saved.

( :large_blue_diamond: CLIENT )

cookie:GetId(): string

That’s right, it returns the cookie’s Id!

cookie:Get(): any

Returns the cookie’s current value.

cookie:Set(value: any)

Set’s the cookies value. This updates the value on the server using a remote.
All cookie data for each player will not save if it exceeds the character limit. The limit can be found near the top of the module.

cookie:SetReliable(reliable: boolean)

If reliable, updating will use a RemoteEvent. Otherwise, it will use an UnreliableRemoteEvent when updating.

cookie:IsActive(): boolean

Returns whether the cookie is active or not.
If unactive, the cookie will not save. If a cookie fails to load in the first place, it will also be inactive.

Conclusion


You can get the model here!
If the link doesn’t work, you can also get it here: Download (5.1 KB)
If the asset isn’t public, you may have to remove the PackageLink inside the module! Roblox is giving me issues when I try to make it public.

Find a bug? Reply with the script that causes it, as well as background information on what you’re trying to do.

Made something cool with this? We’d love to see what you’ve made! :grin:

:+1:

16 Likes

I wish roblox released a way to actually store data on an user’s machine.

It could be useful for stuff like saving game settings

3 Likes

Complicated due to storage. If a player plays ~100 games and all of those are saved on the client, that’s a lot of user storage eaten up. (Don’t even ask about execution on mobile.)

1 Like

I mean roblox could add a limit to how much data can be stored per game inside the device, its not like storing a text file containing a string or something would weight more than a MB

1 Like

The functionality is there, they just restricted it to plugins. Also, in hindsight game settings may be important for the server to read too!

2 Likes

I mean yeah, but the client could still send its settings to the server via remotes, and it would work exactly the same.

idk I just think it would be a cool feature to have the ability to save data to a user’s machine.

1 Like

I made this module to avoid this type of interaction, though. Using remotes to change things only the client would need to worry about was annoying, and restricted creative freedom. I kept coming up with ideas using something like this module, so I just had to make it.

1 Like

Why would you prefer locally-saved settings above cloud-saved settings?

Cloud-saved settings won’t be lost and syncs to multiple devices.


Is there a reason for defaulting to unreliable events? Unreliable events are made for visual effects like moving your head or showing particles. The order and delivery isn’t guaranteed.

For saving notes (or any other important user data), aren’t the downsides of unreliability & size limit outweighing the bandwidth benefits?

A more reliable solution would be to packet updates yourself, so any changes would be queued and sent in bulk per x seconds.

1 Like

Cookies are reliable by default! I set it to false in that example because it used RenderStepped. Notes don’t necessarily need to be updated every frame, though. Just wanted to show how it worked.

3 Likes

Made a much better drawing system with this!

SaveDrawing2.rbxm (12.8 KB)

I did need to fix something in the Cookie module, though. I updated the model on the website… but:

image

It still won’t let me make it public. :sweat_smile:

Fix

Change line:

if self._Value ~= value then

To:

if self._Value ~= value or type(value) == "table" then

Setting the cookie’s value to a table was fine, but trying to set to the same thing would be ignored, even if the table contents may have changed. It wasn’t a problem with the old drawing because it was a new table each time.

3 Likes

Just pushed a small update! A warning is created when you don’t require the module on the server. All the server needs to do is require it, and nothing else!

I still can’t make the package public, so I’m going to file a bug report. Here’s the fixed version:

Cookie.rbxm (5.1 KB)

I’ll update all the download links to use this.

100% I’ll use this module in my projects. Good job

1 Like

Just checked this out, this seems super resourceful! Will absolutely be using this is my future projects. Thanks for sharing this

1 Like