AlliedModders

AlliedModders (https://forums.alliedmods.net/index.php)
-   Snippets and Tutorials (https://forums.alliedmods.net/forumdisplay.php?f=112)
-   -   [INC] Kvizzle - KeyValues made easy (https://forums.alliedmods.net/showthread.php?t=233432)

F2dk 01-14-2014 20:00

[INC] Kvizzle - KeyValues made easy
 
Introduction

When working with KeyValues, it can sometimes be frustratingly complex to do seemingly simple tasks.
Therefore I made something similar to CSS Selectors, just for KeyValues. I call it Kvizzle.

The idea is that, instead of manually navigating up and down in the KeyValues structure, you simply describe the path with a string. In the string, a dot represents a child.

Let us look at this sample KeyValues file.
Code:

"Root"
{
        "Arena1"
        {
                "Name"    "Arena #1"
                "Players"  "10"
                "Author"
                {
                        "Name"        "F2"
                        "Email"        "[email protected]"
                }
        }
}

Let us say you want to fetch the value of "Players".
The path would be: Arena1.Players

If you want to get the name of the author, the path is: Arena1.Author.Name

The code would look like this:
PHP Code:

new players KvizGetNum(kv0"Arena1.Players");

decl String:authorName[64];
KvizGetString(kvauthorNamesizeof(authorName), """Arena1.Author.Name"); 

  • kv is the handle to the KeyValues structure
  • 0 and "" are the default values to be returned if path is not found
  • "Arena1.Players" and "Arena1.Author.Name" are the paths to the values in the KeyValues structure

Okay, so far it is pretty simple. Let us step it up a notch.

Kvizzle supports something called "pseudo classes".
We can use them to describe a path that is not simply given by a name. For example, "first child" is a pseudo class.

Pseudo classes are indicated by using a colon instead of a dot.

This path would get us to the Author's name: Arena1.Author:first-child
To get the value of Players, we could use: Arena1:nth-child(2)

Kvizzle also supports different "actions".
For example, if you want to count how many children Arena1 has, you could use: Arena1:count
This would return 3.

Finally, you can also use "checks".
Let us say you want to find the child to Arena1 that has the value 10.
To do this you can use
  • The pseudo class :any-child, which will loop through all the children until the given check has been fulfilled
  • The check :has-value(text), which will check if the current node has the given value text
  • The action :key, which will return the key-name of the current node.
So the path will be: Arena1:any-child:has-value(10):key
This will return "Players".
PHP Code:

decl String:keyName[64];
KvizGetString(kvkeyNamesizeof(keyName), """Arena1:any-child:has-value(10):key"); 

Documentation

Pseudo Classes
  • :first-child - navigates to the first child
  • :last-child - navigates to the last child
  • :nth-child(n) - navigates to the nth child (specified in the parentheses)
  • :any-child - will look through all children, until the given check has been fulfilled
  • :parent or :up - navigates up to the parent

Checks
  • :has-value - checks if the current node has a value (ie. is not a section)
  • :has-value(text) - checks if the current node has the value given in text (case sensitive)
  • :has-value-ci(text) - checks if the current node has the value given in text (case insensitive)

Actions (can only be used once at the end of the path)
  • :count - Returns the number of children
  • :key or :section-name - Returns the key name (aka section name)
  • :value - Returns the value (this is the default action and is therefore normally omitted)
  • :value-or-section - If the node has a value, that is returned. Otherwise the section name is returned.

Functions
PHP Code:

KvizEscape(String:dest[], destLen, const String:src[])

Handle:KvizCreate(const String:name[])
Handle:KvizCreateFromFile(const String:name[], const String:file[])
bool:KvizToFile(Handle:kv, const String:file[], const String:kvizPath[] = ""any:...)
bool:KvizClose(Handle:kv) {

bool:KvizGetStringExact(Handle:kvString:value[], valueLen, const String:path[], any:...)
bool:KvizGetString(Handle:kvString:value[], valueLen, const String:defVal[], const String:path[], any:...)
bool:KvizSetString(Handle:kv, const String:value[], const String:path[], any:...)

bool:KvizGetNumExact(Handle:kv, &value, const String:path[], any:...)
KvizGetNum(Handle:kvdefVal, const String:path[], any:...)
bool:KvizSetNum(Handle:kvvalue, const String:path[], any:...)

bool:KvizGetFloatExact(Handle:kv, &Float:value, const String:path[], any:...)
Float:KvizGetFloat(Handle:kvFloat:defVal, const String:path[], any:...)
bool:KvizSetFloat(Handle:kvFloat:value, const String:path[], any:...)

bool:KvizGetVectorExact(Handle:kvFloat:value[3], const String:path[], any:...)
bool:KvizGetVector(Handle:kvFloat:vec[3], const Float:defVal[3], const String:path[], any:...)
bool:KvizSetVector(Handle:kvFloat:vec[3], const String:path[], any:...)

bool:KvizGetColorExact(Handle:kv, &r, &g, &b, &a, const String:path[], any:...)
bool:KvizGetColor(Handle:kv, &r, &g, &b, &adefRdefGdefBdefA, const String:path[], any:...)
bool:KvizSetColor(Handle:kvrgba, const String:path[], any:...)

bool:KvizGetUInt64Exact(Handle:kvvalue[2], const String:path[], any:...)
bool:KvizGetUInt64(Handle:kvvalue[2], const defVal[2], const String:path[], any:...)
bool:KvizSetUInt64(Handle:kvvalue[2], const String:path[], any:...)

bool:KvizDelete(Handle:kv, const String:path[], any:...)

bool:KvizExists(Handle:kv, const String:path[], any:...)

bool:KvizJumpToKey(Handle:kvbool:create, const String:path[], any:...)
bool:KvizGoBack(Handle:kv)
bool:KvizRewind(Handle:kv

A description of each function and parameter can be found in the include.

Quick Guide

To use Kvizzle, it is important that you only use the Kviz* functions. Using the normal Kv* functions will mess up Kvizzle's functionality.

You must always begin with KvizCreate/KvizCreateFromFile, and you must always end with KvizClose.

Here is a quick example that you can try out:
Code:

new Handle:kv = KvizCreate("Test");
KvizSetNum(kv, 10, "Top.Key1");
KvizSetNum(kv, 20, "Top.Key2");
KvizSetNum(kv, 30, "Top.Key3");

PrintToChatAll("Key2: %i", KvizGetNum(kv, 0, "Top:nth-child(%i)", 2)); // Prints 20

KvizClose(kv);

It is important that you always use the KvizCreate()/KvizCreateFromFile() and KvizClose(kv) functions when working with Kvizzle. You should never use CreateKeyValues()/FileToKeyValues() or CloseHandle(kv) together with Kvizzle.

Performance

I made some performance measurements. I will post them here soon but until then, the conclusion is:
  • If you are using KeyValues in OnPluginStart, OnMapStart or some other place where performance is not critical, then Kvizzle's performance is good enough for you. (If you are reading from a file, then it will be about 10% slower in total.)
  • If you are using KeyValues in OnGameFrame or some other performance-critical place, then you should not use Kvizzle. (If you are not reading from a file, it will be about 50-150% slower in total.)

Changelog
  • v1.0.0 (15/01/2014)
    • Supports having several KVs open simultaneously
    • Pseudo-Classes (like :first-child, :nth-child(n), :any-child)
    • Checks (like :has-value(val))
    • Actions (like :count, :section-name)
    • A traversal stack (with KvizJumpToKey, KvizGoBack and KvizRewind)
    • Escaping in path (with backslash)
    • Read from file and write to file

-> Download Kvizzle <-

If you are interested, you can also download a unit test of Kvizzle with a lot of examples.

Feedback

Since this is the first release, there is plenty of room for improvement.
So if you have any comments, questions, or suggestion, feel free to write them here.

F2dk 01-14-2014 20:01

Re: [INC] Kvizzle - KeyValues made easy
 
Examples

I have a couple of examples from real projects. Here you can see the difference between using the normal Kv-functions, and using Kvizzle. This is just to give you an idea about how much time you can save by using Kvizzle.

Example 1

Without Kvizzle:
PHP Code:

AddSpawnToArena(Handle:kv, const String:map[], arena, const String:role[], Float:origin[3], Float:angles[3]) {
    if (
KvJumpToKey(kvmaptrue) == false)
        
SetFailState("No arenas for this map");
    
    if (
KvGotoFirstSubKey(kv) == false
        
SetFailState("Missing arenas for this map");
    
    new 
carena 1;
    do {
        if (
carena != arena) {
            
KvJumpToKey(kvroletrue);
            
KvJumpToKey(kv"Spawns"true);
            
            new 
spawnid 1;
            if (
KvGotoFirstSubKey(kv)) {
                do {
                    
spawnid++;
                } while (
KvGotoNextKey(kv));
                
KvGoBack(kv);
            }
            
            
decl String:spawnidstr[8];
            
IntToString(spawnidspawnidstrsizeof(spawnidstr));
            
            if (
KvJumpToKey(kvspawnidstrtrue)) {
                
KvSetVector(kv"origin"origin);
                
KvSetVector(kv"angles"angles);
                
KvGoBack(kv);
            }
            
            
KvGoBack(kv); // "Spawns"
            
            
KvGoBack(kv); // role
            
break;
        }
        
        
carena++;
    } while (
KvGotoNextKey(kv));
    
KvGoBack(kv); // List arenas
    
    
KvGoBack(kv); // map


With Kvizzle:
PHP Code:

AddSpawnToArena(Handle:kv, const String:map[], arena, const String:role[], Float:origin[3], Float:angles[3]) {
    
decl String:escapedMap[64];
    
KvizEscape(escapedMapsizeof(escapedMap), map);
    new 
spawnCount KvizGetNum(kv0"%s:nth-child(%i).%s.Spawns:count"maparenarole);
    
KvizSetVector(kvorigin"%s:nth-child(%i).%s.Spawns.%i.origin"escapedMaparenarolespawnCount+1);
    
KvizSetVector(kvangles"%s:nth-child(%i).%s.Spawns.%i.angles"escapedMaparenarolespawnCount+1);


Example 2

Without Kvizzle:
PHP Code:

bool:FindAttribVal(Handle:kv, const String:prefab[], const String:attrib[], String:value[], valueLen) {
    new 
bool:found false;
    if (
KvJumpToKey(kv"prefabs"false)) {
        if (
KvJumpToKey(kvprefabfalse)) {
            if (
KvJumpToKey(kv"attributes"false)) {
                if (
KvGotoFirstSubKey(kv)) {
                    do {
                        
KvGetString(kv"attribute_class"curAttrsizeof(curAttr));
                        if (
StrEqual(curAttrattribfalse)) {
                            
KvGetString(kv"value"valuevalueLen);
                            
found true;
                            break;
                        }
                    } while (
KvGotoNextKey(kv));
                    
KvGoBack(kv);
                }
                
KvGoBack(kv);
            }
            
KvGoBack(kv);
        }
        
KvGoBack(kv);
    }
    return 
found;


With Kvizzle:
PHP Code:

bool:FindAttribVal(Handle:kv, const String:prefab[], const String:attrib[], String:value[], valueLen) {
    
decl String:escapedPrefab[64], String:escapedAttrib[64];
    
KvizEscape(escapedPrefabsizeof(escapedPrefab), prefab);
    
KvizEscape(escapedAttribsizeof(escapedAttrib), attrib);
    return 
KvizGetStringExact(kvvaluevalueLen"prefabs.%s.attributes:any-child.attribute_class:has-value(%s):parent.value"escapedPrefabescapedAttrib);



F2dk 01-14-2014 20:02

Re: [INC] Kvizzle - KeyValues made easy
 
reserved

Root_ 01-14-2014 20:34

Re: [INC] Kvizzle - KeyValues made easy
 
Wow this is really awesome. :mrgreen:

TnTSCS 01-15-2014 13:58

Re: [INC] Kvizzle - KeyValues made easy
 
Very clean. This will help a lot of people save a lot of time and headache.

Powerlord 01-15-2014 14:29

Re: [INC] Kvizzle - KeyValues made easy
 
I'd be tempted to use this, but most of the time when I'm parsing KeyValues files I'm going to be looping over a section. Something that the default KeyValues implementation does pretty well and would incur heavier costs to do with Kvizzle.

Also, should I assume the name Kvizzle is a reference to Sizzle, one of jQuery's core parts?

dordnung 01-15-2014 15:27

Re: [INC] Kvizzle - KeyValues made easy
 
This looks really awesome! Great job!

friagram 01-15-2014 15:58

Re: [INC] Kvizzle - KeyValues made easy
 
Quote:

Originally Posted by Powerlord (Post 2086326)
I'd be tempted to use this, but most of the time when I'm parsing KeyValues files I'm going to be looping over a section. Something that the default KeyValues implementation does pretty well and would incur heavier costs to do with Kvizzle.

Also, should I assume the name Kvizzle is a reference to Sizzle, one of jQuery's core parts?

Not to mention if you have to do other things in said loops, like filter certain values, or do any task other than just fetch a value. It's better to just learn how to do it right, which doesn't take that long anyway.

F2dk 01-15-2014 16:12

Re: [INC] Kvizzle - KeyValues made easy
 
Quote:

Originally Posted by Powerlord (Post 2086326)
I'd be tempted to use this, but most of the time when I'm parsing KeyValues files I'm going to be looping over a section. Something that the default KeyValues implementation does pretty well and would incur heavier costs to do with Kvizzle.

Also, should I assume the name Kvizzle is a reference to Sizzle, one of jQuery's core parts?

Well observed. It is indeed a reference to Sizzle. :)

Actually, looping through a section is quite easy with Kvizzle.

Let's say you have a structure like this:
Code:

"Root"
{
        "Admins"
        {
                "F2"
                {
                        "SteamID"        "STEAM_0:0:100"
                        "Access"        "z"
                }
                "Powerlord"
                {
                        "SteamID"        "STEAM_0:0:200"
                        "Access"        "x"
                }
                "Black-Rabbit"
                {
                        "SteamID"        "STEAM_0:0:300"
                        "Access"        "y"
                }
        }
}

To loop through each of the admins, you could do:
PHP Code:

for (new 1KvizExists(kv"Admins:nth-child(%i)"i); i++) {
    
decl String:admin[64], String:access[32];
    
KvizGetStringExact(kvadminsizeof(admin), "Admins:nth-child(%i):key"i);
    
KvizGetStringExact(kvaccesssizeof(access), "Admins:nth-child(%i).Access"i);
    
PrintToChatAll(kv"Admin '%s' has access '%s'"adminaccess);


I have optimized this kind of access, so if there are 10 children, internally KvGotoFirstSubKey is called once and KvGotoNextKey is called 10 times, just like when you use the normal Kv functions. So the overhead is mostly the parsing of the string, and that's really not something you should be worried about when using it OnPluginStart, OnMapStart, etc.

If you are doing nested loops, you will end up with very long paths, like: "Admins:nth-child(%i):nth-child(%i):nth-child(%i)". To avoid these long strings, you can do something like this:
PHP Code:

for (new 1KvizJumpToKey(kv"Admins:nth-child(%i)"i); KvizGoBack(kv), i++) {
    
decl String:admin[64], String:access[32];
    
KvizGetStringExact(kvadminsizeof(admin), ":key");
    
KvizGetStringExact(kvaccesssizeof(access), "Access");
    
PrintToChatAll(kv"Admin '%s' has access '%s'"adminaccess);


Also, sometimes when you loop through children, it is because you are searching for something.
For example, imagine that you have a SteamID and you want to check if that SteamID belongs to an admin. Normally you would loop through all admins, and check their SteamID. With Kvizzle you can avoid that, and write a one-liner:
PHP Code:

KvizExists(kv"Admins:any-child.SteamID:has-value(%s)""STEAM_0:0:200"); 


Mathias. 01-15-2014 16:38

Re: [INC] Kvizzle - KeyValues made easy
 
Nice job, I love it. I have an idea to fix the issue of "To use Kvizzle, it is important that you only use the Kviz* functions. Using the normal Kv* functions will mess up Kvizzle's functionality.", maybe clone the handle on your side so people can't mess up the functionality of Kvizzle?


All times are GMT -4. The time now is 18:35.

Powered by vBulletin®
Copyright ©2000 - 2024, vBulletin Solutions, Inc.