Ru:KeyValues (SourceMod Scripting)

From AlliedModders Wiki
Jump to: navigation, search

KeyValues это простая древовидная структура используемая для хранения вложенных разделов состоящих из пар ключ/значение. Детальная информация про KeyValues может быть найдена на Valve Developer Wiki.

Все KeyValues функции в этом документе находятся в public/include/keyvalues.inc.

Введение

KeyValues состоит из узлов и разделов, которые содержат пары ключей и значений. Разделы выглядят примерно так:

"раздел"
{
	"ключ"	"значение"
}

Сторка "раздел" является именем раздела. Строка "ключ" это имя ключа и строка "значение"" является значением.

KeyValues структуры создаются с помощью CreateKeyValues(). Handle должен быть освобожден после завершения работы для устранения утечки памяти.

Файлы

KeyValues могут быть экспортированы и импортированы с помощью KeyValues файлов. Эти файлы всегда состоят из главного узла и множества под-ключей или подразделов. Например файл может выглядеть вот так:

"MyFile"
{
	"STEAM_0:0:7"
	{
		"name"		"crab"
	}
}

В этом примере STEAM_0:0:7 это раздел под главным узлом MyFile.

Для загрузки KeyValues из файла используйте FileToKeyValues. Для сохранения KeyValues в файл используйте KeyValuesFromFile. Для обоих случае вы всегда должны иметь Handle ссылающийся на KeyValues структуру.

Перемещение

SourceMod обеспечивает нативное перемещение по KeyValues структуре. Однако очень важно понимать как происходит это перемещение. Внутренне, SourceMod отслеживает из двух частей информации:

  • Главный узел
  • Стек перемещения

Поскольку структура KeyValues изначально рекурсивная (это дерево), стек перемещения используется для сохранения истории каждого перемещения (перемещение начинается рекурсивно погружаться в дерево, где текущий уровень вложенности углубляется на одну секцию). верх этого стека где все операции имеют место..

Например, KvJumpToKey будет пытаться найтим под-ключ под текущим разделом. При успехе позиция в дереве изменяется перемещением вниз на один уровень, и, следовательно, эта позиция помещается в стек перемещения. Теперь операции такие как KvGetString будут использовать эту новую позицию.

Это нативные изменяют положение, но не меняют стек перемещения. Например, KvGotoNextKey будет менять текущий раздел перед просмотром, но это не является push/pop операциями для стека перемещения. Это потому, что уровень вложенности не изменился; он проходит в следующий раздел на том же уровне, скорее чем найти секцию уровнем глубже.

Больше нативных перемещений:

  • KvGotoFirstSubKey - Находит первый подраздел ниже текущей позиции. Это помещает раздел на стек перемещения.
  • KvGoBack - Берет из стека перемещения (поднимается на уровень выше).
  • KvRewind - Стирает из стека перемещения текущую позицию в главном узле.

Основной Просмотр

Давайте возьмем наш пример MyFile. Как мы можем получить имя "crab" учитывая Steam ID?

bool:GetNameFromSteamID(const String:steamid[], String:name[], maxlength)
{
	new Handle:kv = CreateKeyValues("MyFile");
	FileToKeyValues(kv, "myfile.txt");
	if (!KvJumpToKey(kv, steamid))
	{
		return false;
	}
	KvGetString(kv, "name", name, maxlength);
	CloseHandle(kv);
	return true;
}

Обратите внимание: KvJumpToKey является нативным перемещением, что изменения уровней вложенности стека перемещения. Однако CloseHandle не будет случайно закрывать текущий уровень структуры KeyValues. Это закроет всю вещь.

Повторяющийся Просмотр

Давайте модифицируер предыдущий пример для использования повторов. Это имеет ту же функциональность, но демонстрирует, как для просмотра в течение многих разделах.

bool:GetNameFromSteamID(const String:steamid[], String:name[], maxlength)
{
	new Handle:kv = CreateKeyValues("MyFile");
	FileToKeyValues(kv, "myfile.txt");
 
	if (!KvGotoFirstSubKey(kv))
	{
		return false;
	}
 
	decl String:buffer[255];
	do
	{
		KvGetSectionName(kv, buffer, sizeof(buffer));
		if (StrEqual(buffer, steamid))
		{
			KvGetString(kv, "name", name, maxlength);
			CloseHandle(kv);
			return true;
		}
	} while (KvGotoNextKey(kv));
 
	CloseHandle(kv)
	return false
}

Обратите внимание: В этом примере отметим что KvGotoNextKey является итеративной функции, не однократное перемещение. Так как это не меняет уровень вложенности, мы не должны вызывать KvGoBack для возврата и продолжения итерации.

Полное Перемещение

Например мы хотим просмотреть каждую секцию файла KeyValues. Пример как это может выглядеть:

BrowseKeyValues(Handle:kv)
{
	do
	{
		if (KvGotoFirstSubKey(kv))
		{
			BrowseKeyValues(kv);
			KvGoBack(kv);
		}
	} while (KvGotoNextKey(kv));
}

Эта функция будет просматривать всю структуру KeyValues. Обратите внимаеие, что каждый успешный вызов KvGotoFirstSubKey сопровождается вызовом KvGoBack. Это потому, что первый помещается в стек перемещения. Мы должны взять позицию для того, чтоб могли продолжить просмотр с нашей старой позиции.

Удаление

Есть два пути для удаления секции из структуры KeyValues:

  • KvDeleteKey - Безопасное удаление имени подраздела и все дочерние секции/ключи.
  • KvDeleteThis - Безопасное удаление текущей позиции, если это возможно.

Простое Удаление

Для начала используем наш пример "базовый просмотр" для удаления секции Steam ID:

RemoveSteamID(const String:steamid[])
{
	new Handle:kv = CreateKeyValues("MyFile")
	FileToKeyValues(kv, "myfile.txt")
	if (!KvGotoFirstSubKey(kv))
	{
		return
	}
	KvDeleteKey(kv, steamid)
	KvRewind(kv)
	KeyValuesToFile(kv, "myfile.txt")
	CloseHandle(kv)
}

Обратите внимание: Мы вызвали KvRewind так файл будет сбрасываться с корневой позиции, вместо текущей.

Повторяющееся Удаление

Кроме того давайте покажем как ранее повторяющийся пример может быть адаптирован для удаления раздела. Для этого мы используем KvDeleteThis, который удаляет текущую позицию из предыдущей позиции в стеке перемещения.

RemoveSteamID(const String:steamid[])
{
	new Handle:kv = CreateKeyValues("MyFile")
	FileToKeyValues(kv, "myfile.txt")
 
	if (!KvGotoFirstSubKey(kv))
	{
		return
	}
 
	decl String:buffer[255]
	do
	{
		KvGetSectionName(kv, buffer, sizeof(buffer))
		if (StrEqual(buffer, steamid))
		{
			KvDeleteThis(kv)
			CloseHandle(kv)
			return
		}
	} while (KvGotoNextKey(kv))
 
	CloseHandle(kv)
}

Полное Удаление

Наконец давайте посмотрим как удалить все (или некоторые) ключи. KvDeleteThis как особое свойство которое может действовать в качестве автоматического итератора. Когда удаляется ключ автоматически переходим к следующему, как если бы вызвали KvGotoNextKey. Если больше нет ключей просто берет стек перемещения.

Пример удаление всех SteamID-ов содержащие пустые имена:

DeleteAll(Handle:kv)
{
	if (!KvGotoFirstSubKey(kv))
	{
		return
	}
 
	for (;;)
	{
		decl String:name[4]
		KvGetString(kv, name, sizeof(name))
		if (name[0] == '\0')
		{
			if (KvDeleteThis(kv) < 1)
			{
				break
			}
		} else if (!KvGotoNextKey(kv)) {
			break
		}	
	}
}

Хотя это и выглядет как бесконечный цикл, это не так. Как только KvDeleteThis вернет неудачу при нахождении ключа, произойдет разрыв цекла. Аналогично, если KvGotoNextKey вернет неудачу, цикл закончится.