Garry's Mod Wiki

Revision Difference

File_Based_Storage#527763

<cat>Dev.Lua</cat> # Introduction Data Storage is what we use when we want data to persist through map changes and server restarts. <note>Never trust the client with data. Only store, save, change, and load data from the server. If you need to give data to the client, make sure to keep a backup of it on the server!</note> There are multiple ways of storing data in Garry's Mod. In this tutorial, we will go over File Based Storage # Rules with data-handling When handling any data, there are some rules that we should follow: * **Never** trust data that has been handled by a client. Keep all data backed up on the server as this is the only way that you can be sure that the data is authentic. Let's say we started letting clients keep track of their money; what's stopping them from adding a few zeros on the end? * **Always** use identification that the client can't manipulate. If we wanted to store a client's health when they log off, and we used their steam names as identification, what's stopping them from changing it before they next log on? Nothing. This is why we must use identification such as <page>Player:SteamID</page>'s, since they can never be changed by the client. In case you couldn't tell, there is an over-arching theme here: **Never trust the client**. Unfortunately, Garry's mod servers aren't predictable - they can easily crash without warning or become overloaded. Therefore, you should deal with the data as quickly as possible to avoid losing it. Do not wait until the player leaves to save the number of tokens that they have earned whilst playing, as the server may suffer a fatal crash before then. Furthermore, you should not decide to store data during the <page>GM:ShutDown</page> hook, as it may be too late to start saving data as some player's will have become invalid and that means their precious token count is now lost. However, you may be reading this and you think that your data should be saved at the very last minute, or is really not important enough to be saved every time it is altered - in which case, you can use the <page>GM:ShutDown</page> hook at your own risk! # About Text files are a simple way of storing data. File Based Storage does not require any external modules, as it relies solely on the <page>file</page> library and is pretty straightforward. For these reasons, it is the recommended method for beginners. # Storing tables - util.TableToJSON This is, perhaps, the easiest method for beginners that will circumvent frustration when converting data types such as <page>Color</page> or <page>Vector</page>, so we will start with this method first. ## Turning tables into strings A text file is basically a string of characters. This means that we need to convert all of our data into a string of characters... <page>util.TableToJSON</page> is a function that will turn a Lua table into the JSON format. JSON is a popular format of representing certain data (such as Lua tables) as text, which can be saved in a file and turned back into the same data later. Here is an example of how we can use <page>util.TableToJSON</page>: ```lua -- Here we have a generic table, containing different types of data that we wish to save into a file: local storeOwnerData = { pos = Vector(1000,20,3), rot = Angle(0,90,0), col = Color(255,0,0,20), text = "Dave - Store Owner" } -- Let's see what util.TableToJSON is going to give us to work with: print(util.TableToJSON(storeOwnerData)) ``` Output: ``` {"text":"Dave - Store Owner","rot":"{0 90 0}","pos":"[1000 20 3]","col":{"r":255.0,"b":0.0,"a":20.0,"g":0.0}} ``` Notice how the Color, Angle and Vector structures get converted into distinguishable string structures? These unique formats allows the reversing function <page>util.JSONToTable</page> to convert the formats back into their original data structures. Let's write some further code so that we can see reversal process that will need to take place when reading the file. ```lua -- JSONData now stores the Lua table "storeOwnerData" in JSON format. local JSONData = util.TableToJSON(storeOwnerData) local converted = util.JSONToTable(JSONData) PrintTable(converted) ``` Output: ``` col: a = 20 b = 0 g = 0 r = 255 pos = 1000.000000 20.000000 3.000000 rot = 0.000 90.000 0.000 text = Dave - Store Owner ``` As you can see, the JSON string has been converted back into a table format. Furthermore, we can check that the data types have been returned to their original format by using <page>Global.isvector</page> with `converted.pos ` and <page>Global.isangle</page> with `converted.rot` which tell us that they have been converted back into a <page>Vector</page> and <page>Angle</page>. Unfortunately, passing `converted.col` into <page>Global.IsColor</page> highlights that it has not been correctly converted into the <page>Color</page> structure. Fortunately, we can write some code to convert it back to normal later on. As you can see, the JSON string has been converted back into a table format. Furthermore, we can check that the data types have been returned to their original format by using <page>Global.isvector</page> with `converted.pos` and <page>Global.isangle</page> with `converted.rot` which tell us that they have been converted back into a <page>Vector</page> and <page>Angle</page>. Unfortunately, passing `converted.col` into <page>Global.IsColor</page> highlights that it has not been correctly converted into the <page>Color</page> structure. Fortunately, we can write some code to convert it back to normal later on. ## Saving JSON data Now we've learnt how to convert a table into a storable format, and also how to reverse the stored format into a normal Lua table - we can now learn how to read/write files and save the JSON data. Believe it or not, saving data to a file only requires one function: <page>file.Write</page>. Upon looking at the documentation we learn that it only needs two things: * A filename * Data to save So let's go ahead and and write some code to finish saving the storeOwnerData table into a file called `storedata.txt`: ```lua local storeOwnerData = { pos = Vector(1000,20,3), rot = Angle(0,90,0), col = Color(255,0,0,20), text = "Dave - Store Owner" } -- Let's put it in a function so that when we next change the table, we can just call this function again function SaveStoreData() local converted = util.TableToJSON(storeOwnerData) file.Write("storedata.txt", converted) end SaveStoreData() ``` That's it, that's the file saved! If you want to view the file for your own eyes, you can go into the `garrysmod/data` folder and the file will be called "storedata.txt". ⤶ <note>Since [Septembre 2019](https://gmod.facepunch.com/blog/september-2019-update), files can be saved into JSON (.json) format.</note>⤶ ## Reading JSON data from files, and converting it back to a table Reading JSON data is just as easy with the key function now being <page>file.Read</page> that takes one argument... the Filename. Here we will also convert the table containing color data into a Color structure too: ```lua local storeOwnerData = {} function ReadStoreData() -- Make sure you use the same filename as the one in file.Write! local JSONData = file.Read("storedata.txt") -- JSONData is currently a JSON string - let's convert that into a table: storeOwnerData = util.JSONToTable(JSONData) -- Remember how the col value does not get converted into a Color structure? Let's fix that: local oldCol = storeOwnerData.col storeOwnerData.col = Color(oldCol.r, oldCol.g, oldCol.b, oldCol.a) end ``` The variable `storeOwnerData` now contains all of the data and the `col` value is now a <page>Color</page>! # Writing data types other than tables The above method is excellent for storing tables, but what if we just want to store a number or a string? Nothing is particularly different in this scenario, we just don't need the util functions anymore: ## Saving the string ```lua local myString = "This is a string that I want to save!" function SaveString() -- We are saving the contents of myString into a file called "stringdata.txt" file.Write("stringdata.txt", myString) end SaveString() ``` ## Loading the string ```lua local myString = "" function LoadString() myString = file.Read("stringdata.txt") end LoadString() ``` Not that difficult at all, is it? # Alternatives to file based storage Let's say that we needed to keep track of a player's money count. Then, we would need to save to the file every time the player either leaves or has their money count updated, and thus, a 1,000 line file (or even more!) will need to be read, altered, and then completely overwritten with new data. This is slow, and it will get much slower the more players have their data stored in a file. Therefore, we need an alternative. Perhaps the most efficient method of storing large amounts of data in Garry's mod is through SQL. Unfortunately, SQL is not as easy to learn as file based storage, however, it is completely worth it if you do decide to learn. ⤶ PData could be a viable alternative, however, as of June 2020, I cannot recommend <page>Player:SetPData</page>, as PData has a major issue that has not been resolved yet, which can cause players to share data with a different player entirely. The issue is currently on the SetPData page, and if it is ever removed, then you are probably safe to use it! Perhaps the most efficient method of storing large amounts of data in Garry's mod is through [SQL](https://wiki.facepunch.com/gmod/sql). Unfortunately, SQL is not as easy to learn as file based storage, however, it is completely worth it if you do decide to learn. ⤶ <warning>PData could be a viable alternative, however, as of June 2020, I cannot recommend <page>Player:SetPData</page>, as PData has a major issue that has not been resolved yet, which can cause players to share data with a different player entirely. The issue is currently on the SetPData page, and if it is ever removed, then you are probably safe to use it.</warning>