Допустим имеется некий скрипт, к примеру, выключатель, от состояния которого может зависеть состояние множества других скриптов. Назовём этот скрипт (объект) — Устройство (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);
}
}
}
}
}
}
}









