[Include] y_ini - Fast INI file reading and writing.
#1

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:

Код:
#include <YSI\y_ini>
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:

Код:
#define MAX_INI_ENTRY_TEXT 160
#include <YSI\y_ini>
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:

Код:
gA = dini_Get("myini.ini", "c");
gB = dini_Get("myini.ini", "b");
gC = dini_Get("myini.ini", "a");
And this ini file:

Код:
a = 10
b = 67
c = 42
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:

Код:
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 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.

Код:
new
	INI:ini = INI_Open("myini.ini");
INI_WriteString(ini, "NAME", "******");
INI_WriteInt(ini, "SCORE", gScore);
INI_WriteFloat(ini, "HEALTH", health);
INI_Close(ini);
Deleting

You can also delete values from a file:

Код:
new
	INI:ini = INI_Open("myini.ini");
INI_RemoveEntry(ini, "NAME");
INI_Close(ini);
That will remove the "NAME" field from the current tag (see below). Simple as that. Deletions can also be mixed with writes:

Код:
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);
Tags

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
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.
  • Reading
To read in only the LVDM data you would do:

Код:
INI:filename[LVDM](name[], value[])
{
	INI_Float("health", gHealth);
	if (!strcmp(name, "pos") && !sscanf(value, "fff", gX, gY, gZ))
	{
		return;
	}
}
Here the "LVDM" from the tag is actually embedded in the function prototype. The general format is:

Код:
INI:filename[tagname](name[], value[])
{
}
Or, as seen in the example above, an empty tagname for tagless data.
  • Writing
To write data to a tag simply first set the tag:

Код:
new
	INI:ini = INI_Open("myini.ini");
INI_SetTag(ini, "LVDM");
INI_WriteString(ini, "NAME", "******");
INI_WriteInt(ini, "SCORE", gScore);
INI_Close(ini);
Functions
  • 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);
    }
    Save an integer for a player (assuming you used a matching INI_Load call):
    Код:
    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);
    Loads the given file and passes the loading functions extra data (for example a player id):
    Код:
    INI_Load(filename, true, playerid);
    Код:
    INI:filename[tag](playerid, name[], value[])
    {
    }
    Load an INI file, and send the loaded data to all running scripts:
    Код:
    INI_Load(filename, .bLocal = false);
    Load an INI file, and send the loaded data to all running scripts:
    Код:
    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.
Advanced

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.
This function deserves a section of it's own.

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[])
You would set "remoteFormat" to:

Код:
"User_%s"
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:
Код:
forward LoadOneFile(tag[], name[], value[]);
public LoadOneFile(tag[], name[], value[])
{
}
Код:
INI_ParseFile("myini.ini", "LoadOneFile", .bPassTag = true);
Load a whole file for a single player at once:
Код:
forward LoadOneUser(playerid, tag[], name[], value[]);
public LoadOneUser(playerid, tag[], name[], value[])
{
}
Код:
INI_ParseFile(playerfile, "LoadOneUser", .bExtra = true, .extra = playerid, .bPassTag = true);
Load a single tag for a single player:
Код:
forward LoadOneUser_LVDM(playerid, name[], value[]);
public LoadOneUser_LVDM(playerid, name[], value[])
{
}
Код:
INI_ParseFile(playerfile, "LoadOneUser_%s", false, true, playerid);
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.
Код:
forward LoadOneUser_LVDM(playerid, name[], value[]);
public LoadOneUser_LVDM(playerid, name[], value[])
{
}
Код:
INI_ParseFile(playerfile, "LoadOneUser_%s", false, true, playerid, false);
Load a file by filename, passing the tag to the function, with extra data and broadcast:
Код:
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);
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):

Код:
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
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:

Код:
((6009 * 3) + 91159) / 4 = 27296.5
So, the final results are:

Код:
yini: 1564
dini: 63810
emu : 27297
SII: 52807
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.
Reply


Messages In This Thread
y_ini - Fast INI file reading and writing. - by corne - 14.04.2015, 21:39
Re: y_ini - Fast INI file reading and writing. - by Uberanwar - 26.04.2015, 01:31
Re: y_ini - Fast INI file reading and writing. - by AlonGlenn - 27.11.2016, 00:28
Re: y_ini - Fast INI file reading and writing. - by Jelly23 - 27.11.2016, 00:37
Re: y_ini - Fast INI file reading and writing. - by Viggo - 31.01.2018, 20:01
Re: y_ini - Fast INI file reading and writing. - by DJefferson - 31.03.2018, 19:08
Re: y_ini - Fast INI file reading and writing. - by ForCop - 07.04.2018, 06:32
Re: y_ini - Fast INI file reading and writing. - by CantBeJohn - 07.04.2018, 14:01

Forum Jump:


Users browsing this thread: 1 Guest(s)