/FreedumbStore

A Roblox Datastore wrapper that avoids the limitations by being absolutely ridiculous. Use at your own risk.

Primary LanguageLuaMIT LicenseMIT

FreedumbStore

A Roblox Datastore wrapper that avoids the limitations by being absolutely ridiculous. Use at your own risk.

This system avoids the 6 second cooldown (key hopping), bypasses the 4MB limit (auto-chunking), supports storing Roblox datatypes like Vector3 & Color3 (serializing), and allows you to write from multiple servers at once (atomic locking).

Store massive tables of Roblox datatypes and update them rapidly! (This is a bad idea, don't actually do that. Use responsibly.)

Example

local FreedumbStore = require(Packages:WaitForChild("FreedumbStore"))
local Store = FreedumbStore.new("AuctionHouse_V0.1.1", "Trades")
-- In datastore AuctionHouse_V0.1.1 at the key Trades, we're gonna fill a giant dictionary

-- We can flip on some debug prints if we want
Store:_setDebug(true)

for ID=1, 50 do
	-- Create tons of random data
	local data = table.create(ID*4096)
	for i=1, ID*4096 do
		data[i] = string.char(math.random(40, 120))
	end
	data = table.concat(data)

	print("Storing trade #" .. ID)
	Store:SetAsync("TradeID-" .. ID, {
		TradeData = data,
		Position = Vector3.new(math.random(100), math.random(100), math.random(100)),
	}):timeout(6):catch(warn):await()
end

Store:GetAsync("TradeID-25"):timeout(6):andThen(function(trade25)
	print("Trade 25:", trade25)
end):catch(warn)
Store:GetAllAsync():timeout(6):andThen(function(trades)
	print("All Trades:", trades)
end):catch(warn)

Installation

Wally:

[server-dependencies]
FreedumbStore = "boatbomber/freedumbstore@0.7.5"

Rojo:

rojo build default.project.json -o FreedumbStore.rbxm

API

function FreedumbStore.new(name: string, primaryKey: string): FreedomStore

Returns a new FreedumbStore.

function FreedumbStore:Destroy(): ()

Destroys the FreedumbStore. Will yield before destroying if there's a write operation in-progress when called.

function FreedumbStore:GetAsync(key: string, useCache: boolean?): any?

Returns the value at that key of the table. useCache defaults to true.

function FreedumbStore:GetAllAsync(useCache: boolean?): {[any]: any}

Returns the entire table. Large and slow, use sparingly. useCache defaults to true.

function FreedumbStore:GetChunkAsync(chunkIndex: number, useCache: boolean?): {[any]: any}

Returns the section of the table at that chunk index. useCache defaults to true.

function FreedumbStore:ClearCache(): ()

Clears the cached chunks.

function FreedumbStore:SetAsync(key: string, value: any): ()

Sets the value at that key of the table.

function FreedumbStore:UpdateAsync(key: string, callback: (oldValue: any?) -> any?): any

Update the value at that key of the table.

function FreedumbStore:RemoveAsync(key: string): boolean

Removes a key and its value from the table. Returns a success boolean.

function FreedumbStore:SetChunkAsync(chunkIndex: number, chunk: any): ()

[BE CAREFUL WITH THIS] Sets the entire chunk.

function FreedumbStore:UpdateChunkAsync(chunkIndex: number, callback: (any?) -> any?): ()

Update an entire chunk.

function FreedumbStore:OnChunkChanged(listener: (chunkIndex: number, chunk: any) -> ()): () -> ()

Adds a listener callback for chunk changes. Returns a disconnect function.

function FreedumbStore:GetChunkIndexOfKey(key: string): number?

[INTERNAL] Returns the chunk index that a given key is stored in, if exists.

function FreedumbStore:SetChunkIndexOfKey(key: string, chunkIndex: number): ()

[INTERNAL] Sets the chunk index that the given key is stored in.

function FreedumbStore:FindAvailableChunkIndex(): number

[INTERNAL] Returns the first chunk index that is not full.

function FreedumbStore:FireChunkChanged(chunkIndex: number)

[INTERNAL] Fires chunk changed listeners.

function FreedumbStore:AquireLock(chunkIndex: number): ()

[INTERNAL] Aquires the global lock for writing to that chunk. Yields until lock is received.

function FreedumbStore:ReleaseLock(chunkIndex: number): ()

[INTERNAL] Releases the global lock to that chunk, if we have it.

Budget

You'll still need to respect the total calls budget, sadly. Can't be truly free.

Read the budget here and here or wherever Roblox moved the docs to by the time you read this.

Budget Cost per function:

A is available chunks, C is used chunks, / is BestCase/WorstCase

Function Datastore Gets Datastore Sets Memorystore Gets Memorystore Sets
new 0 0 0 0
OnChunkChanged 0 0 0 0
FindAvailableChunkIndex 1/2*A 0 1+A 0
AquireLock 0 0 1+? 1
ReleaseLock 0 0 0 1
GetChunkIndexOfKey 0/1 0 1 0
SetChunkIndexOfKey 0 0 0 1
GetChunkAsync 1/2 0 1 0
GetAsync 1/3 0 2 0
GetAllAsync 1/2*(1+C) 0 1+C 0
SetChunkAsync 1 1 1 1/2
UpdateChunkAsync 2/3 1 3+? 3/4
SetAsync 3/5*A 1 5+A+? 3/5
UpdateAsync 3/5*A 1 5+A+? 3/5
RemoveAsync 2/4 1 4+? 4/5