14.04.2015, 21:39
Introduction
This is a bit of a clean up of the old ini system in YSI. Not much has actually changed, just a few bug fixes and added support for tagless ini files such as dini files.
I will in the next few days release a user system based on this, as well as a wrapper to allow modes using dini to take advantage of the improved file reading and writing.
Tutorial
There is now a tutorial on reading and writing y_ini files:
How to use y_ini
Download
This library is a part of YSI, which can be found here. Keep your eye on that topic and your server log for updates.
YSI Download Topic
Use
To use this simply include the header:
If you want to save whirlpool hashes with this system, due to an oversight on my part you need to increase the max buffer size:
Reading
This system differs in use quite significantly from dini, but with good reason. In dini you open a file, search for a single value, read that value, close the file and repeat. Imagine this code:
And this ini file:
If you can't tell that will open and close the file 3 times and read 6 lines in, just for 3 values - surely you should just open the file once and read 3 lines? This is the equivalent y_ini code:
The code is a little longer, but it's much faster, only reading the file once. Esentially y_ini uses callbacks to load files instead of explicitly reading individual values, which means files are read in the order they are stored. I intend to add the option to load individual values later, but there's rarely any need if you do things properly - read a file and get the data, it's not going to change unless you change it.
The syntax of the file is also a little odd, but we'll come to that later.
Note that the trailing semicolon used to be a bug, it's now not.
Writing
y_ini writes values in a very similar way to dini, though you explicitly open and close the file - dini opens and closes it on every value. In addition it buffers writes - you don't need to worry about exactly how it works, it just means that it writes lots of values at once.
Deleting
You can also delete values from a file:
That will remove the "NAME" field from the current tag (see below). Simple as that. Deletions can also be mixed with writes:
Tags
The other feature in yini not in dini is tags within ini files:
If you had two modes (or even just two libraries) in dini, any user system would need to use separate files for each one. Wheras here the different modes' data are separated by tags (the bits in square brackets). This is where the odd syntax in the read function came in.
Here the "LVDM" from the tag is actually embedded in the function prototype. The general format is:
Or, as seen in the example above, an empty tagname for tagless data.
Functions
There is one function not covered above, which is the function INI_Load calls:
Often to read in files "INI_Load" will do, but that is very restrictive in what functions it calls. If you have user files based on their name you would need a function for every user ever, which is clearly impossible, so you want to remove the filename from the function being called. This is where the "remoteFormat", "bFileFirst" and "bPassTag" parameters come in.
remoteFormat - This defines the format of the function to call, in standard "format" structure. There are two string parameters it can take - the current filename and the current tag within the file. For example, if you have a function defined as:
You would set "remoteFormat" to:
The first %s is the current tag, the second %s (both are optional) is the filename.
bFileFirst - This swaps the order of the data passed to the function format, if this is true (default false) the first %s in the remoteFormat is the filename and the second is the tag.
bPassTag - If this is true it adds an extra parameter to the callback with the current tag, so you can load an entire file from a single function. The tag name comes immediatedly before the "name" parameter, so after any optional extra parameter:
Load a whole file at once:
Load a whole file for a single player at once:
Load a single tag for a single player:
Load an INI file from one script and broadcast the data around the server. Very useful for admin/user filterscripts to allow every mode to load their own data.
Load a file by filename, passing the tag to the function, with extra data and broadcast:
Note that the additional parameter can be anything, not just a playerid and not just an int (though it can't be a string or an array). It just happens to have been designed for playerids.
Timings
Timing comparisons to dini and SII. This is loading 400000 values from ini files of a modest size (less than 64 entries (42)). Note that the "less than 64" is important - by default yini can buffer 64 values when pretending to be dini (which is a feature in my local version but not yet released):
I'll just explain some of the results. As I've been saying for years - dini, while popular, is INSANELY slow, here it is almost 45x slower than yini.
The blatantly obvious conclusion from these results is use yini as it was designed, however if you can't there are ways to get around it.
The two examples of yini posing as dini are the two extremes. The yini/dini system reads the file and buffers it. If the file is still in the buffer when the next request from the same file arrives the result is returned from memory instead of from re-reading the file as dini does.
The first of these two results is where you read many values all at once. In this version the file is read once and buffered, then all the data is got from the buffer. This is frankly the most likely outcome.
The second version, which is actually slower than dini, is where you read a few values, then read other ini files, then read more values. yini can buffer up to four files (by default) at once, however if you read your dini file, then read four other files, the system will dump the dini file to make room in memory. yini's dini emulation takes slightly longer to read a file than dini, but it only reads the file once in general, instead of meny times - which is where the speed comes from. In this version it has to do a slightly longer read and buffering many times, the worst of both worlds.
In general if you are using dini emulation mode you're likely to get an average weighted heavily towards the faster time, especially if you don't open ini files and leave them open:
So, the final results are:
Averaging the two extremes of the emulation mode, assuming you're more likely to have less than four files open at once, gives over a 2x speed up for yini in emulation mode and the original 45x speed up for regular yini.
Credits
I am reposting this include (made by ******) with the thought of what he said to several members of the community. Everything that could help someone should not be deleted from the forums, which is something I agree with since I learned a lot by reading the tutorials on here. He made a lot of things that are used by lots of servers and that knowledge should not be lost for present and future developers.
This is a bit of a clean up of the old ini system in YSI. Not much has actually changed, just a few bug fixes and added support for tagless ini files such as dini files.
I will in the next few days release a user system based on this, as well as a wrapper to allow modes using dini to take advantage of the improved file reading and writing.
Tutorial
There is now a tutorial on reading and writing y_ini files:
How to use y_ini
Download
This library is a part of YSI, which can be found here. Keep your eye on that topic and your server log for updates.
YSI Download Topic
Use
To use this simply include the header:
Код:
#include <YSI\y_ini>
Код:
#define MAX_INI_ENTRY_TEXT 160 #include <YSI\y_ini>
This system differs in use quite significantly from dini, but with good reason. In dini you open a file, search for a single value, read that value, close the file and repeat. Imagine this code:
Код:
gA = dini_Get("myini.ini", "c"); gB = dini_Get("myini.ini", "b"); gC = dini_Get("myini.ini", "a");
Код:
a = 10 b = 67 c = 42
Код:
INI:myini[](name[], value[]) { INI_String("a", gA, len_of_gA); INI_String("b", gB, len_of_gB); INI_String("c", gC, len_of_gC); return 0; // This is now required. }
Код:
INI_Load("myini.ini");
The syntax of the file is also a little odd, but we'll come to that later.
Note that the trailing semicolon used to be a bug, it's now not.
Writing
y_ini writes values in a very similar way to dini, though you explicitly open and close the file - dini opens and closes it on every value. In addition it buffers writes - you don't need to worry about exactly how it works, it just means that it writes lots of values at once.
Код:
new INI:ini = INI_Open("myini.ini"); INI_WriteString(ini, "NAME", "******"); INI_WriteInt(ini, "SCORE", gScore); INI_WriteFloat(ini, "HEALTH", health); INI_Close(ini);
You can also delete values from a file:
Код:
new INI:ini = INI_Open("myini.ini"); INI_RemoveEntry(ini, "NAME"); INI_Close(ini);
Код:
new INI:ini = INI_Open("myini.ini"); INI_WriteString(ini, "NAME", "******"); INI_WriteInt(ini, "SCORE", gScore); INI_RemoveEntry(ini, "NAME"); INI_WriteFloat(ini, "HEALTH", health); INI_Close(ini);
The other feature in yini not in dini is tags within ini files:
Код:
[LVDM] health = 4.23 pos = 2500 1968 7.3 [SFTDM] health = 100 pos = -2134 -980 2
- Reading
Код:
INI:filename[LVDM](name[], value[]) { INI_Float("health", gHealth); if (!strcmp(name, "pos") && !sscanf(value, "fff", gX, gY, gZ)) { return; } }
Код:
INI:filename[tagname](name[], value[]) { }
- Writing
Код:
new INI:ini = INI_Open("myini.ini"); INI_SetTag(ini, "LVDM"); INI_WriteString(ini, "NAME", "******"); INI_WriteInt(ini, "SCORE", gScore); INI_Close(ini);
- INI_Int
name[] - Name of the INI textual identifier.
variable - Variable to store to.
Macro to ease the loading of integers from INI files. Data is passed to the loading callback as a string, this will convert it to an integer (using sscanf if possible, normal functions if not) and save in the given variable. First checks that the name of the data passed matches the given name. Note that for this macro to work the variables MUST be called "name" and "value".
Examples:
Save an integer:
Код:INI:filename[tag](name[], value[]) { INI_Int("LEVEL", gLevel); }
Код:INI:filename[tag](playerid, name[], value[]) { INI_Int("PLAYER_LEVEL", gLevel[playerid]); }
- INI_Float
name[] - Name of the INI textual identifier.
variable - Variable to store to.
Saves the passed value as a float if the name matches. - INI_Hex
name[] - Name of the INI textual identifier.
variable - Variable to store to.
Saves the passed value as a hew number if the name matches. - INI_Bin
name[] - Name of the INI textual identifier.
variable - Variable to store to.
Saves a number in the form 0b1001101 (binary) if the name matches. - INI_Bool
name[] - Name of the INI textual identifier.
variable - Variable to store to.
Saves the passed value as a boolean (true/false) if the name matches. - INI_String
name[] - Name of the INI textual identifier.
variable - Variable to store to.
length - Length of the destination array.
Saves the passed value as a string if the name matches. - INI_Load
filename[] - The file to load.
bool:bExtra - Send additional data.
extra - Additional data to send.
bLocal - Call local functions instead of gloabal ones.
Reads in a whole file. Everything except the filename is optional.
Examples:
Loads the given file:
Код:INI_Load(filename);
Код:INI_Load(filename, true, playerid);
Код:INI:filename[tag](playerid, name[], value[]) { }
Код:INI_Load(filename, .bLocal = false);
Код:INI_Load(filename, .bLocal = false);
- INI_Open
filename[] - INI file to open.
Opens an ini file for writing. Returns a handle with the tag type "INI:". - INI_Close
INI:file - Handle to the ini to close.
Closes the currently being written to file. - INI_SetTag
INI:file - INI file handle to write to.
tag[] - Name of the new file subsection for subsequent data to write to.
Sets a new [tag] section header. Subsequent data is written under this header. - INI_RemoveEntry
INI:file - File to write to.
name[] - Item to remove.
Removes an entry from the current tag of the current file. - INI_WriteString
INI:file - File to write to.
name[] - Data name.
data[] - Data.
Write a string to the current ini. - INI_WriteInt
INI:file - File to write to.
name[] - Data name.
data - Integer data.
Write an int to the current ini.
- INI_WriteHex
INI:file - File to write to.
name[] - Data name.
data - Integer data.
Write an int to the current ini as a hex value (0x1F182).
- INI_WriteBool
INI:file - File to write to.
name[] - Data name.
data - Boolean data.
Write a boolean (true/false) to the current ini.
- INI_WriteBin
INI:file - File to write to.
name[] - Data name.
data - Integer data.
Write an int to the current ini in binary format (0b1001010). - INI_WriteFloat
INI:file - File to write to.
name[] - Data name.
Float:data - Float data.
accuracy - number of decimal places to write.
Write a float to the current ini.
There is one function not covered above, which is the function INI_Load calls:
- INI_ParseFile
filename[] - The file to load.
remoteFormat[] - What function to call.
bool:bFileFirst - What order to format the function.
bool:bExtra - Send additional data.
extra - Additional data to send.
bLocal - Call local functions instead of gloabal ones.
bool:bPassTag - What extra data to pass.
Often to read in files "INI_Load" will do, but that is very restrictive in what functions it calls. If you have user files based on their name you would need a function for every user ever, which is clearly impossible, so you want to remove the filename from the function being called. This is where the "remoteFormat", "bFileFirst" and "bPassTag" parameters come in.
remoteFormat - This defines the format of the function to call, in standard "format" structure. There are two string parameters it can take - the current filename and the current tag within the file. For example, if you have a function defined as:
Код:
forward User_LVDM(name[], value[]); public User_LVDM(name[], value[])
Код:
"User_%s"
bFileFirst - This swaps the order of the data passed to the function format, if this is true (default false) the first %s in the remoteFormat is the filename and the second is the tag.
bPassTag - If this is true it adds an extra parameter to the callback with the current tag, so you can load an entire file from a single function. The tag name comes immediatedly before the "name" parameter, so after any optional extra parameter:
Load a whole file at once:
Код:
forward LoadOneFile(tag[], name[], value[]); public LoadOneFile(tag[], name[], value[]) { }
Код:
INI_ParseFile("myini.ini", "LoadOneFile", .bPassTag = true);
Код:
forward LoadOneUser(playerid, tag[], name[], value[]); public LoadOneUser(playerid, tag[], name[], value[]) { }
Код:
INI_ParseFile(playerfile, "LoadOneUser", .bExtra = true, .extra = playerid, .bPassTag = true);
Код:
forward LoadOneUser_LVDM(playerid, name[], value[]); public LoadOneUser_LVDM(playerid, name[], value[]) { }
Код:
INI_ParseFile(playerfile, "LoadOneUser_%s", false, true, playerid);
Код:
forward LoadOneUser_LVDM(playerid, name[], value[]); public LoadOneUser_LVDM(playerid, name[], value[]) { }
Код:
INI_ParseFile(playerfile, "LoadOneUser_%s", false, true, playerid, false);
Код:
forward Load_myini(playerid, tag[], name[], value[]); public Load_myini(playerid, tag[], name[], value[]) { }
Код:
INI_ParseFile("myini.ini", "Load_%s", true, true, playerid, false, true);
Timings
Timing comparisons to dini and SII. This is loading 400000 values from ini files of a modest size (less than 64 entries (42)). Note that the "less than 64" is important - by default yini can buffer 64 values when pretending to be dini (which is a feature in my local version but not yet released):
Код:
dini: 63810 - Yes, that's just over 1 minute yini: 1564 - Yes, that's just over 1 second yini as dini 1: 6009 - Just over 6 seconds yini as dini 2: 91159 - About a minute and a half SII: 52807
The blatantly obvious conclusion from these results is use yini as it was designed, however if you can't there are ways to get around it.
The two examples of yini posing as dini are the two extremes. The yini/dini system reads the file and buffers it. If the file is still in the buffer when the next request from the same file arrives the result is returned from memory instead of from re-reading the file as dini does.
The first of these two results is where you read many values all at once. In this version the file is read once and buffered, then all the data is got from the buffer. This is frankly the most likely outcome.
The second version, which is actually slower than dini, is where you read a few values, then read other ini files, then read more values. yini can buffer up to four files (by default) at once, however if you read your dini file, then read four other files, the system will dump the dini file to make room in memory. yini's dini emulation takes slightly longer to read a file than dini, but it only reads the file once in general, instead of meny times - which is where the speed comes from. In this version it has to do a slightly longer read and buffering many times, the worst of both worlds.
In general if you are using dini emulation mode you're likely to get an average weighted heavily towards the faster time, especially if you don't open ini files and leave them open:
Код:
((6009 * 3) + 91159) / 4 = 27296.5
Код:
yini: 1564 dini: 63810 emu : 27297 SII: 52807
Credits
I am reposting this include (made by ******) with the thought of what he said to several members of the community. Everything that could help someone should not be deleted from the forums, which is something I agree with since I learned a lot by reading the tutorials on here. He made a lot of things that are used by lots of servers and that knowledge should not be lost for present and future developers.