Jump to content


Универсальный сервер для хранения параметров


  • Please log in to reply
1 reply to this topic

#1 SOb Zemlja

SOb Zemlja

    Активный участник

  • Главные администраторы
  • PipPipPipPip
  • 801 posts
  • Пол:М
  • Откуда:Россия, Москва
  • Основной цех:Строители
  • Второй цех:Скриптеры
  • SL Status: 

Posted 19.04.09 - 18:26

Задача: написать систему скриптов, которая бы позволила обмениваться произвольным набором параметрами с произвольным количеством объектов в асинхронном режиме.

Допустим имеется некий скрипт, к примеру, выключатель, от состояния которого может зависеть состояние множества других скриптов. Назовём этот скрипт (объект) — Устройство (Device, dev). Другие скрипты в данной схеме равнозначны, т.е. также могут иметь состояния и переменные, влияющие на работу частей системы.

Самый простой вариант: при смене состояния узла системы он сообщает, к примеру, через llSay всем об этом.

Очевидно, что какие-то части системы могут быть заняты и вовремя не отреагировать на подобную «широковещательную» рассылку. А другим частям она и вовсе окажется не нужна. Или окажется нужно значение другого параметра, которое рассылалось 5 минут назад. Или ещё что-то… В общем вариант, когда каждый узел системы обрабатывает каждую рассылку и хранит весь вагон меняющихся параметров у себя — не вариант. Да и лично мне больше нравится централизованное хранение. Разумеется есть ситуации, когда оно не применимо, надо смотреть индивидуально. А пока — делаем Сервер (Server, srv).

Также мы знаем, что все наши скрипты очень заняты, поэтому обмен данными сервер<->устройство должен быть асинхронным. Ни кто ни кого не ждёт, никаких подтверждений. Приём, который позволяет сделать асинхронную обработку сообщений чата я описывал. Вкратце: llMessageLinked строит очередь из сообщений, сам. Т.е. нам надо в отдельном скрипте (прокси) получить сообщение и передать его через llMessageLinked на тот-же самый прим. В случае, если обработчик в основном скрипте занят, сообщение будет в очереди, пока он не освободится. Выглядит прокси для сервера очень просто:

integer gSrvChannel = 100

default
{
	state_entry()
	{
		llListen(gSrvChannel, "", NULL_KEY, "");
	}

	listen(integer channel, string name, key id, string message)
	{
		llMessageLinked(LINK_THIS, 0, message, NULL_KEY);
	}
}

Сервер… сервер хранит у себя пары параметр=значение, в «базе данных». По команде вида «Set, <имя параметра>, <значение параметра>» устанавливает новое значение для параметра или создаёт в базе данных новую запись, если такого параметра в неё ещё не добавляли. По команде вида «Get, <имя параметра>, <адрес устройства>» — возвращает значение.

Вопрос — а кому возвращает? Мы договорились, что обмен асинхронный. Следовательно ответа сервера никто «на линии» не ждёт. Логично в команду Get добавить параметр, который сообщает серверу, кому именно требуется ответ, т.е. на каком канале слушает устройство, пославшее запрос (т.н. адрес устройства). В этом месте у меня возникла проблема. Т.к. канал устройства выбирается случайным образом при инициализации и делается это в скрипте прокси, основной скрипт не знает номер канала. Да, можно его вписать в Description объекта, но это как-то не то… К тому же мне иногда нужно было сбрасывать канал. И лучше сбрасывать прокси, чем основной скрипт. В общем я сделал «регистрацию» устройства на сервере, которую выполняет прокси-скрипт каждый раз при сбросе. Для сервера это команда Key, в которой передаётся пара ключ=канал. Команда Get, соответственно подписывается ключом, а сервер, извлекая из базы регистрации номер канала «кричит» в него ответ. Всё происходит в асинхронном режиме. Вот так выглядит прокси-скрипт для устройства:

integer gSrvChannel = 100;

default
{
	state_entry()
	{
		integer lDevChannel = -(integer)llFrand(2147483648);
		string lCommand = "Key";
		llSay(gSrvChannel, llList2CSV([lCommand, llGetKey(), lDevChannel]));
		llListen(lDevChannel, "", NULL_KEY, "");
	}

	listen(integer channel, string name, key id, string message)
	{
		llMessageLinked(LINK_THIS, 0, message, NULL_KEY);
	}
}

Вот пример скрипта, который отправляет на сервер параметр (Set, name, 0):

integer gSrvChannel = 100;

default
{
	state_entry()
	{
		string lCommand = "Set";
		string lParamName = "name";
		integer lParamValue = 0;
		llSay(gSrvChannel, llList2CSV([lCommand, lParamName, lParamValue]));
	}
}

А этот скрипт каждые 0.5 секунд запрашивает параметр:

integer gSrvChannel = 100;

default
{
	state_entry()
	{
		llSetTimerEvent(0.5);
	}

	timer()
	{
		string lCommand = "Get";
		string lParamName = "name";
		llSay(gSrvChannel, llList2CSV([lCommand, lParamName, llGetKey()]));
	}
}

Обработчик полученного ответа будет находиться в link_message.

Ну и сам сервер…

list gKeysDB = [];
list gParamsDB = [];

default
{
	link_message(integer sender_number, integer number, string message, key id)
	{
		list lRequest = llCSV2List(message); // превращаем команду в список

		if(llGetListLength(lRequest) > 1)
		{
			string lCommand = llList2String(lRequest, 0); // в позиции 0 сама команда

			if(lCommand == "Key")
			{
				key lDevKey = llList2Key(lRequest, 1);
				integer lDevChannel = llList2Integer(lRequest, 2);
				integer lDevKeyPos = llListFindList(gKeysDB, [lDevKey]);

				if(lDevKeyPos == -1) // проверка записи в базе, если не найдена — добавить, если найдена — обновить
				{
					gKeysDB += [lDevKey, lDevChannel];
				}
				else
				{
					gKeysDB = llListReplaceList(gKeysDB, [lDevKey, lDevChannel], lDevKeyPos, lDevKeyPos + 1);
				}
			}
			else
			{
				string lParamName = llList2String(lRequest, 1);
				integer lParamPos = llListFindList(gParamsDB, [lParamName]);

				if(lCommand == "Set")
				{
					integer lParamValue = llList2Integer(lRequest, 2);

					if(lParamPos == -1)
					{
						gParamsDB += [lParamName, lParamValue];
					}
					else
					{
						gParamsDB = llListReplaceList(gParamsDB, [lParamName, lParamValue], lParamPos, lParamPos + 1);
					}
				}

				if(lCommand == "Get")
				{
					if(lParamPos != -1)
					{
						key lDevKey = llList2Key(lRequest, 2);
						integer lDevKeyPos = llListFindList(gKeysDB, [lDevKey]);

						if(lDevKeyPos != -1)
						{
							integer lDevChannel = llList2Integer(gKeysDB, lDevKeyPos + 1);
							integer lParamValue = llList2Integer(gParamsDB, lParamPos + 1);
							llSay(lDevChannel, (string)lParamValue);
						}
					}
				}
			}
		}
	}
}


#2 Viktor Zeplin

Viktor Zeplin

    Новичок на форуме

  • Пользователи
  • PipPip
  • 3 posts
  • Пол:М
  • Основной цех:Скриптеры
  • Второй цех:Строители
  • SL Status: 

Posted 21.08.09 - 16:55

Интересно! Когда-то мне срочно была такая штука, но потом Destr подсказал как сделать аналог двумерного массива и потребность отпала. Вот кстати:

// Простой двумерный массив (by Destr Hax)
string sfArray (integer iY, integer iX, list lArray, integer iSize) {
	return llList2String(lArray, iX + iY * iSize);
}
list lfArray(integer iY, integer iX, list lArray, integer iSize, string sNew) {
	integer iPos = iX + iY * iSize;
	return (list) llListReplaceList(lArray, [sNew], iPos, iPos);
}

Функция чтения: sting sArray = sfArray(...);
и записи: list lArray = lfArray(...);




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users