I need help about scripting

so i started learning scripting about 5-6 months ago,
and i need help about structuring Data saves, using modulo scripts,
and i wanna someone to show me Very basic structure of data save script (modulo)
and explain how Data saves works inside modulo scripts,

i would be Grateful if you help me. Thanks :slightly_smiling_face:

sorry if i didnt explain it well or if theres gramma issues or i misspelled

2 Likes

Well, the best thing for you would be to learn English (non-ironically) and not confuse a mathematical modulo operation with a module script
Secondly you should not learn dogmatic code patterns™ like module script balkanization
Thirdly, the module script is just a regular script that runs once (well, duh, like every script) upon being required and caches the returned value.

This is very useful to share upvalue edit functions or cached data like from a datastore
Althrough you have to be smart about it
Avoid treating tables like structs
Unfortunately I do not have nor do I know a good tutorial about datastores, but I plan to write one in the future
As a beginner, the important thing is to write your own code and not outsource anything to libraries (you should not outsource any code unless you are making a slop prolefeed cash grab quickly).
You can learn library sources for their implementation, but don’t outsource knowledge to them

2 Likes

basically, DataStoreService handles the actual persistent storage, while the ModuleScript just gives the rest of your server a clean way to load/read/change/save it. usually you’d GetAsync() when the player joins, keep their data in a table while they’re playing, then UpdateAsync() periodically + when they leave.

so something like DataService.Get(player).Coins += 10 should normally just modify the cached table, not immediately make another DataStore request every time because that would be kinda stupid.

I’d probably have one ModuleScript managing a table like {Coins = 0, Level = 1, XP = 0}, then a normal server Script calls DataService.Load(player) / DataService.Save(player) from PlayerAdded and PlayerRemoving.

also make sure DataStore calls are wrapped in pcall, and don’t give somebody default data if their load request outright failed, because then you can accidentally overwrite their real data when they leave. Then over and over again, basically free money essentially.

you can make this WAY more complicated with autosaving, retries, session locking, migrations, etc… which if I recommend that I will get smited by the guy above me for suggesting usage of libraries/frameworks

3 Likes

Don’t teach begginers to write table struct cope slop please

yes you will
Deploying tactical nuke… :warning: :radioactive: :radio:

They are all slop reinventing the metadata argument passed in the save parameter at datastore
Literally 10 minutes to write better jobid locking

1 Like

I’m not teaching him to manually pack Coins into byte offsets, tf did you want me to give him { 0, 1, 0 }???

Wtf did I just do this is what I meant to send

image

Just stop acting like it’s complex
You can also use arrays for it aswell
Just don’t get beginners hooked into a bad habit.
Smart formatting solves all the problems

Array:

local data = {
	[1] = 0;--Coins
	[2] = 1;--Level
	[3] = 0;--XP
}

With a buffer it’s pretty easy too

--[[Data
+0 Coins
+4 Level
+8 XP
]]

local data = buffer.create(12)

buffer.writeu32(data,0,0)
buffer.writeu32(data,4,1)
buffer.writeu32(data,8,0)

or you can be even smarter and cache it even further

Don’t act like it’s something complex

Caching default value optimization example:

Also, you can generate a cache buffer if you use PrePack, for example [Free] PrePack - Visual Buffer Serializer & Binary Struct Editor for Roblox

local cache_default = {
	[1] = 0;--Coins
	[2] = 1;--Level
	[3] = 0;--XP
}

local data = table.clone(cache_default)


local data = buffer.fromstring("\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00")
1 Like

Since you’re starting out, instead of making a module from scratch you should probably use the DataStore Module “ProfileStore”. With it it’s going to be simpler saving player data and more features that you can check out. you can learn the basics of how it works with these tutorials:
https://youtu.be/evBhoqeYegQ?si=1SdB1uz44TB_0GIu
https://youtu.be/m2SP_TLeWHI?si=ZSQ0QtMBRgEt3I5h

But if you really want to make it manually then I think what @Kooraseru said at first is pretty good.

if we’re gonna do the optimization argument then I’d bulk serialize this when sending the DataStore update, not make the buffer the format every script has to work with.

the runtime representation[1] should be optimized around how that data is accessed, then dirty tracked. when it’s time to save, you hit the serialization boundary[2], and that’s where I’d actually pack it into the buffer.

if we’re talking genuinely large-scale data, you’d also separate hot data[3] from cold data[4] instead of pretending every field benefits from the exact same memory layout.

making every script know that offset 4 means Level isn’t some final form of optimization, you just spread the serialization format across the entire runtime.

and again, this is someone learning how DataStores work. you cannot tell me a framework is too complicated and then teach him to maintain a handwritten memory layout everywhere, let alone make up your own serializer – tsk tsk I thought better of you Yarik


so I actually benchmarked the exact thing I’m talking about in Luau Playground.

2,000,000 runtime updates with a normal table + one bulk serialization at the end took ~140.3ms.

keeping the data in a buffer for the entire runtime took ~283.2ms, so about 2.02x slower in this test.

and that’s the point I’m making: buffers are good for storage density and serialization, but that doesn’t mean the serialized representation should also be your hot runtime representation.

if the game touches the data millions of times and saves it once, I’d rather pay the packing cost once instead of paying buffer read/write costs every single mutation.

you optimized the format being sent to storage, then made the entire runtime use that format too.

very optimized Yarik

(if you don’t believe the run, re-order the test runs if you’re worried about warm-cache/order effects)


  1. the copy of the player’s data that the server is constantly reading/changing while they’re actually playing ↩︎

  2. the point where the runtime data gets converted into the compact format meant for storage ↩︎

  3. stuff accessed constantly ↩︎

  4. stuff barely touched ↩︎

1 Like

ProfileStore is a bloatslop
It reinvents the wheel with 99999 confusing bloat abstraction runtime tax to literally Do:

local DataStoreService = game:GetService("DataStoreService")

local datastore = DataStoreService:GetDataStore("data")

datastore:SetAsync(tostring(userid),data,nil,metadata)

Also its like 4K+ lines holy hell bro

It’s literally faster
buffer read is faster than table read 10% to 100% times faster depending on context.
But both of them are faster than a table-struct bloatslop prolefeed anyway.
Also, most of the time you just use variables, and you don’t need an actual continuous data representation due to logic being self contained.

You just don’t create any serializer bloat at all, lol.
Just some callbacks batched through opcode order invoking specific functions to handle the following bytes of this opcode if you plan the game to have unreliable data order for different fields.
So mental model

data = [Opcode per field e.g. inventory logic]Following bytes

batching logic = {
[number]: (buffer,offset)->number jump bytes data for the next opcode
}

Also, you make it sound too complex, and it’s 99% of all problems
nothing is complex ever

Just don’t be slow, bro™

that’s why I tested the actual runtime pattern instead of an isolated read.

2,000,000 updates in Luau Playground gave me ~140.3ms for table runtime + one bulk serialization, and ~283.2ms for keeping the runtime data in a buffer. so for the thing I’m actually talking about, your version was ~2.02x slower.

…yes, that’s basically my entire point. use the representation that’s cheap to work with during runtime, then convert it when you actually need the continuous representation.

Yarik that is a serializer… you have an opcode format, byte layout, offset tracking, dispatch table, and callbacks consuming the following bytes. you didn’t remove the serializer, you just described one manually.

the benchmark is right there and reproducible, so if you think the ordering is contaminating it, swap the runs and test it. Heres a video of me literally running it over and over, along with refreshing.

1 Like

Because you did not enable native mode
this changes it significantly

Not the same thing as this general purpose library slop
Because when people imply serliazer on roblox they refer to this slop garbage libraries instead of a smart batching logic

ok so I enabled native mode.

table runtime + bulk serialization: 139.8 ms
buffer runtime:                     294.3 ms
buffer/table runtime ratio:         2.105x

and this benchmark is already biased slightly in your favor because my timed section includes actually packing the final table into the buffer, while your buffer version has nothing left to serialize at the end. sooooooo… suck it ig?

that’s a different argument. a serializer is the thing that converts your runtime representation into a serialized representation. whether it’s some giant general-purpose library or 20 lines of opcode dispatch doesn’t change what it is.

your “smart batching logic” with opcodes, byte offsets, callbacks, and rules for consuming the following bytes is a custom serializer. you can call the libraries bloated if you want, but you can’t redefine the word serializer because you don’t like the popular implementations.


look man, unless you have actual reproducible numbers that beat the benchmark, I’d just take the L here. you can still keep the optimizer title, just maybe drop the “Grand” for this one.

2 Likes

i do not like slop games and i dont make slop games.

2 Likes

Also Thanks everyone who helped me i do understand now more of modulo tables, i do know basics about datastores and pcalls but this still helped me thanks

@irakligeogeo1
DON’T CALL MODULESCRIPTS MODULO TABLES!!!
THEY MEAN COMPLETELY DIFFERENT THINGS!!!

2 Likes

i think you should make a DD post about this, could help a lot of people…

1 Like

i dont know whats DD post. ://