YSI:INI
From SA-MP Wiki
Introduction
The YSI INI system is different to other INI systems about, it is designed to allow fast loading and saving by buffering data to be written and loading an entire file at once. Other systems open the file, scan through for one line, load that line, close the file, reopen the file and repeat - this is insanely inefficient and very bad for your hard disk due to all the repeated accesses. They're the same for writing, they open the file, write a single like, close it, reopen it, write another line, close it again and so on, any sane person has to ask what the point is. The way YSI works is you write all the data you want to save to the file to a big array (called a buffer), once this is done the file is loaded from the disk just once, all the data written to it at the same time and the result stored back to disk, vastly reducing disk access and increasing the life of your hard disk. For loading the system is the exact opposite of other systems - they check the file for a single value, then check for another single value, YSI reads the first line and checks if the script wants it, then the next and the next. In terms of processing this is about the same as other methods however it only requires a single disk access, removing a lot of additional overhead and again reducing disk stress.
Features
- Full INI format support, including tags and comments.
- Comments retained when the value they're with is altered.
- Buffering system for faster writes and reduced disk accessing.
- Callback system for reduced disk access on load.
- Additional optional parameters for callbacks.
Use
Writing
Using the INI system is very simple, to write data just open the file, write data and close the file again:
// Open the file and store the handle new INI:myFile = INI_Open("myfile.ini"); // Check the file opened correctly if (myFile != INI_NO_FILE) { // Set the tag for the following data INI_SetTag(myFile, "tag1"); // Write some data to the file // Write a string INI_WriteString(myFile, "some_data", "hello"); // Write a number INI_WriteInt(myFile, "some_int", 4); // Write a float INI_WriteFloat(myFile, "some_float", 3.0); // Set another tag INI_SetTag(myFile, "tag_the_second"); // Write more data INI_WriteInt(myFile, "more_ints", 101010); // Finally, close the file, writing all the buffered data INI_Close(myFile); }
The code above will result in an ini file looking something like the following:
myfile.ini:
[tag1] some_data = hello some_int = 4 some_float = 3.0 [tag_the_second] more_ints = 101010
If you want to add comments to the file the correct ini comment symbol is ';':
myfile.ini:
[tag1] ; This is the first tag some_data = hello ; This is the first data some_int = 4 ; All this extra text will be ignored some_float = 3.0 ; But remain in a file after editing [tag_the_second] more_ints = 101010
To change a value you would simply do:
new INI:myFile = INI_Open("myfile.ini"); if (myFile != INI_NO_FILE) { INI_SetTag(myFile, "tag1"); INI_WriteInt(myFile, "some_int", 90); INI_SetTag(myFile, "tag_the_second"); INI_WriteInt(myFile, "more_ints", 90); INI_Close(myFile); }
And you would now have:
myfile.ini:
[tag1] ; This is the first tag some_data = hello ; This is the first data some_int = 90 ; All this extra text will be ignored some_float = 3.0 ; But remain in a file after editing [tag_the_second] more_ints = 90
Certain formatting may change but the data will be constant. By default values are separated from their name by ' = ', but any combination of spaces, tabs and equals signs are valid, so the above ini could be represented by:
myfile.ini:
[tag1] some_data hello ; Only a space some_int == == 90 ; Multiple spaces and equals some_float 3.0 ; Tab [tag_the_second] more_ints = 90 ; Tab, equals and multiple spaces
For this reason the following line would be invalid:
INI_WriteInt(myFile, "some int value", 5);
As it would result in the following entry in the INI:
some int value = 5
Meaning the name would be "some" and the value would in fact be "int value = 5".
Also note that data not associated with a tag (i.e. any data at the top of a file above the first tag) will not be read and this code:
new INI:myFile = INI_Open("myfile.ini"); if (myFile != INI_NO_FILE) { INI_WriteInt(myFile, "no_data", 90); INI_Close(myFile); }
Will not do anything as "no_data" has no tag to be written under.
Reading
To read a file you load it and the system then passes all the loaded data back to you for parsing and saving. The data is passed back in a series of public functions named from a combination of a string you pass the load function and the tag name the data is under - this is why no tag means no data loading. Code to load the file above may look something like this:
// Global variables to store the data new some_data_var[32], some_int_var, Float:some_float_var, more_ints_var; forward CustomCallback_tag1(identifier[], text[]); forward CustomCallback_tag_the_second(identifier[], text[]); LoadTheData() { // Call the file load function // The second parameter is the same as public functions with %s where the tag name goes INI_ParseFile("myfile.ini", "CustomCallback_%s"); // So something now all the data has been loaded } // First public custom callback for the "tag1" tag // The %s in "CustomCallback_%s" has been replaced with "tag1" // identifier[] is the name of an ini entry, text[] is the value as a string public CustomCallback_tag1(identifier[], text[]) { // Check if this is the "some_data" string and save it Player_LoadString("some_data", some_data_var); // Else check if it's some_int and convert to a number Player_LoadInt("some_int", some_int_var); // Else check if it's some_float and convert to a float Player_LoadFloat("some_float", some_float_var); return 1; } // Second callback for the other tag public CustomCallback_tag_the_second(identifier[], text[]) { Player_LoadInt("more_ints", more_ints_var); return 1; }
Now all the data has been nicely loaded into the provided global variables for use in your script.
API Functions
These functions are the ones called by a scripter to read and write ini files.
INI_INI()
Currently does nothing but for future compatibility should be called from the script init function. Called automatically by the Script_ system.
bool:INI_ParseFile(filename[], remoteFormat[], bool:bFileFirst = false, bool:bExtra = false, extra = 0, bool:bLocal = false, bool:bPassTag = false)
This function is the main function for loading an ini file. The first two parameters are required as described above. The format parameter, again, as in the example, uses %s to denote the position of the tag in the callback name, you can also add a second %s to add the file name to the callback. The bFileFirst parameter will swap the order of the tag and filename in the callback, when true it will make the first %s the filename and the second (if you have one) the tag name. The '.' in the filename will be replaced with a '_' in the function to make it a legal function name.
With tag:
INI_ParseFile("myfile.ini", "Callback_%s"); public Callback_some_tag(identifier[], text[]) ...
With tag and filename:
INI_ParseFile("myfile.ini", "Callback_%s_%s"); public Callback_some_tag_myfile_ini(identifier[], text[]) ...
With filename and tag:
INI_ParseFile("myfile.ini", "Callback_%s_%s", .bfilefirst = true); public Callback_myfile_ini_some_tag(identifier[], text[]) ...
With filename:
INI_ParseFile("myfile.ini", "Callback_%s", .bFileFirst = true); public Callback_myfile_ini(identifier[], text[]) ...
bExtra and extra are used to pass additional data to the callback functions, e.g. the playerid parameter in the user system. If bExtra is true the value in extra will be passed as a parameter before identifier[]:
INI_ParseFile("myfile.ini", "Callback_%s", .bExtra = true, .extra = playerid); public Callback_some_tag(playerid, identifier[], text[]) ...
By default the callback functions are called globally, so any running script can load data from an ini file being loaded, as with the user system when someone logs in, but setting bLocal true will call the callbacks using CallLocalFunction instead so only the script loading the ini will get the data.
bPassTag when true tells the system to pass the tagname as an additional variable before identifier[] but after any extra data being sent. This does not affect the format parameter so you can pass the tag name to a function with the tag in it's name, which would be a bit pointless. This is more commonly used with either no %s in the format parameter to call a single function or a single %s and bFileFirst true.
No format:
INI_ParseFile("myfile.ini", "Callback", .bPassTag = true); public Callback(tag[], identifier[], text[]) { if (!strcmp(tag, "some_tag")) { ... } }
With file:
INI_ParseFile("myfile.ini", "Callback_%s", .bFileFirst = true, .bPassTag = true); public Callback_myfile_ini(tag[], identifier[], text[]) { if (!strcmp(tag, "some_tag")) { ... } }
bool:INI_Load(filename[])
This is a basic wrapper for INI_ParseFile with a format string of INI_Parse_%s_%s.
INI:INI_Open(filename[])
This function starts the write process, it doesn't actually open a file, just sets up the buffer. On success it returns a handle to the ini with tag INI, on fail it returns INI_NO_FILE (INI:-1):
new INI:file = INI_Open("myfile.ini"); if (file != INI_NO_FILE) { ... }
INI_Close(INI:file)
Writes the buffer for a given ini file to the file and closes the handle:
INI_Close(file);
INI_SetTag(INI:file, tag[])
Sets the current tag for the specified INI file under which subsequent data will be placed:
INI_SetTag(file, "some_tag");
Gives, in the ini:
[some_tag]
INI_WriteString(INI:file, name[], data[])
Writes a string to the specified ini:
INI_WriteString(file, "string_data", "hello");
Gives, in the ini:
string_data = hello
INI_WriteInt(INI:file, name[], data)
Writes a number to the specified ini:
INI_WriteInt(file, "int_data", 4);
Gives, in the ini:
int_data = 4
INI_WriteFloat(INI:file, name[], Float:data, accuracy = 6)
Writes a float to the specified ini:
INI_WriteFloat(file, "float_data", 2.123456789);
Gives, in the ini:
float_data = 2.123456
The optional accuracy parameter specifies the number of decimal places to display the number to, default being 6:
INI_WriteFloat(file, "float_data", 2.123456789, 2);
Gives, in the ini:
float_data = 2.12
Commands
-none-
Compile options
Compile options are defines which can be placed ABOVE the:
#include <YSI>
line to alter how the code is compiled, the compile options specific to the ini system are:
INI_NEW_LINE
#define INI_NEW_LINE "\n"
This is the end of line marker used in writing the files, the default is "\r\n" but you can change this to "\n" if required.
INI_MAX_WRITES
#define INI_MAX_WRITES <num>
This is the maximum number of INI files which can be open for writing at once, the default is 4 and this is possibly a high number, especially given that this is per script.
INI_BUFFER_SIZE
#define INI_BUFFER_SIZE <num>
This is the number of values which can be stored in the buffer before the buffer needs to be flushed. If the buffer fills up while data is still being written the contents are written to file and the buffer reset. The default is 64 which again should be more than enough for most scripts.
MAX_INI_TAGS
#define MAX_INI_TAGS <num>
This is the maximum number of tags you can be writing to a file before the buffer needs flushing. The default is 3 and the maximum due to technical restrictions is 32.
Other functions
These are functions internal in the ini system and should not (and most can not) be called by the scripter.
INI_Int(name[], function)
Deprecated.
INI_Float(name[], function)
Deprecated.
INI_Hex(name[], function)
Deprecated.
INI_Bin(name[], function)
Deprecated.
INI_String(name[], function)
Deprecated.
bool:INI_GetEntryName(source[], dest[], &i)
Extracts the first part of a line from an ini (i.e. the name) and returns the index of the value in i.
INI_BroadcastData(function[], identifier[], text[], local)
Deprecated.
INI_BroadcastDataExtra(function[], extra, identifier[], text[], local)
Deprecated.
INI_GetEntryText(source[], dest[], i)
Gets the second part of a line from an ini file (i.e. the value).
INI_AddToBuffer(INI:file, name[], data[])
The three INI_Write(Int|String|Float) functions are all wrappers for this function. This actually finds an empty slot in the buffer and adds the data as string data to it. If the buffer is full it first calls INI_WriteBuffer
INI_WriteBuffer(INI:file)
This writes all the values from a buffer to a file and is called from INI_AddToBuffer if the buffer is filled or INI_Close to write remaining data. For every tag it finds in the file it first checks all the data associated with that tag in the buffer against data in the file and replaces it if found, it then writes all values not already in the file and moves on to the next tag. After this is writes all data associated with tags not already in the file.