Идея этой публикации родилась после чтения одной очень полезной статьи Javascript и 1С. Кросс-платформенное взаимодействие. В обсуждении этой публикации была затронута интересная проблема:
... события на вебстранице возникают асинхронно, форма 1С, в общем случае, может какие-то из виртуальных кликов пропустить. Тип переменной interactionVariable лучше сделать строкой и не просто присваивать, а дописывать в неё json очередного сообщения. На стороне формы 1С реализовать очередь, в которую складывать элементы массива из interactionVariable и последовательно обрабатывать.
............
... непонятно, как поведет себя одновременный .pop() у этого стека в 1С, и .push() в javascript, при отсутствии mutex'ов.
...........
... Поведёт себя плохо, решение не идеальное, но вероятность проблем ниже.
Однако существует достаточно простой механизм, лишенный вышеуказанных недостатков.
Пусть имеется два процесса: процесс-источник, посылающий данные, и процесс-приемник, эти данные принимающий. Создадим очередь - некую упорядоченную последовательность данных, обращение к которым возможно по индексу. И для 1С и для JavaScript это может быть простой массив. Помимо очереди, сделаем два указателя на начало и конец очереди: head и tail. При возникновении необходимости процесс-источник помещает данные в хвост очереди и сдвигает указатель tail. Процесс-приемник периодически, асинхронно, проверяет наличие данных в очереди, считывает их и сдвигает указатель head. Таким образом head как бы "догоняет" tail. Индикатором наличия данных в очереди является неравенство tail <> head. Как видим, каждый из процессов имеет доступ на запись только к своему указателю, что делает ненужным организацию какого-либо конкурентного доступа к указателям, использование семафоров-mutexов и т.п. Доступ к данным в очереди также разделен: процесс-источник помещает порцию данных до того как сдвигает tail, а процесс-источник обратится к этим данным после того как tail будет сдвинут. Аналогичным образом можно реализовать и систему, в которой получателей будет несколько - каждый процесс-приемник должен работать со своим указателем head, разница между tail и head для каждого получателя будет означать количество еще необработанных им элементов данных.
Практическая реализация этого механизма несложна и может быть реализована в двух вариантах:
- Очередь - обычный буфер. В этом случае создается динамический массив, который растет с увеличением количества передаваемых порций данных. Указатели tail и head также увеличиваются. Для того, чтобы не допустить неограниченного роста массива, перед добавлением очередных данных надо сделать проверку: если очередь пуста, т.е. tail = head, то сбрасываем оба указателя в начало массива. Этот вариант подходит в том случае, если процесс-приемник в целом чаще забирает данные из очереди, чем процесс-источник их туда помещает. В противном случае head никогда не "догонит" tail и мы получим неограниченный рост буфера. Но зато этот вариант гарантирует получение всех данных процессом-приемником.
- Кольцевой буфер. Этот вариант лучше использовать при недостатке памяти для очереди и/или когда помещение данных происходит чаще их получения. Для этого организуется массив с фиксированным количеством элементов. Когда указатель, увеличиваясь, доходит до конца массива, то его значение меняется на начало этого же массива. При росте очереди, т.е. при увеличении tail, необходимо проверять чтобы tail не "догнал сделав круг" указатель head. Если это должно произойти, то следует либо сдвинуть оба указателя, потеряв таким образом самые старые данные, либо не помещать новые данные в очередь, потеряв их. В первом случае мы лишаемся преимущества разделенного доступа к указателям очереди. А что касается второго случая - программисты MS-DOS вспомнят, что именно так организован клавиатурный буфер: по адресу 0040:001E могло быть записано 15 клавиатурных нажатий, а далее происходило "переполнение буфера".
В качестве примера работы рассмотренного механизма создадим обработку, которая будет содержать поле HTML. В поле HTML будем получать координаты указателя мыши при его перемещении и передавать эту информацию в форму 1С. Очередь организуем средствами JavaScript. Данные будем передавать в формате JSON.
Текст поля HTML:
<!DOCTYPE html>
<html style="height: 100%;">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<script>
var queue = Array(); // Очередь событий
var queueHead = -1; // Указатель на начало очереди
var queueTail = -1; // Указатель на конец очереди
var totalEventCount = 0; //Количество обработанных событий
//Помещение произвольных данных в очередь
function pushEvent(dataEvent){
if (queueHead == queueTail && queueHead > -1){ //Если очередь пуста, сбросим указатели
queueHead = queueTail = -1;
}
queue[queueTail + 1] = JSON.stringify(dataEvent);//Преобразуем данные в JSON
queueTail += 1;
totalEventCount += 1;
}
//Получение данных очереди по индексу
function getEvent(index){
return queue[index];
}
//Обработчик события движения указателя мыши
function OnMouseMoveEvent (event) {
//Поместим данные в очередь
pushEvent({x:event.clientX, y:event.clientY});
//Отобразим координаты
document.getElementById("coord").textContent = "X=" + event.clientX + " Y= " + event.clientY + " Total=" + totalEventCount;
//Уведомим форму 1С
button1c.click();
}
</script>
</head>
<body style="height: 100%;">
<div id="coord" style="height: 100%;"></div>
//Невидимая кнопка для уведомления формы 1С
<button id="button1c" style="display: none"></button>
</body>
</html>
При движении мыши вызывается обработчик OnMouseMoveEvent, в котором происходит помещение данных о координатах в очередь и уведомление формы 1С об этом событии. Уведомление осуществляется с помощью эмуляции нажатия невидимой кнопки button1c.
На стороне 1С обработаем событие от поля HTML:
&НаКлиенте
Процедура ХТМЛПриНажатии(Элемент, ДанныеСобытия, СтандартнаяОбработка)
Если ДанныеСобытия.button.id="button1c" Тогда
ПолучитьИОбработатьДанные();
КонецЕсли;
КонецПроцедуры
и, для надежности, получение и обработку данных добавим в обработчике ожидания:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ПодключитьОбработчикОжидания("ПолучитьИОбработатьДанные",1);
КонецПроцедуры
Процедура получения данных:
&НаКлиенте
Процедура ПолучитьИОбработатьДанные()Экспорт
ОкноДок=ЭтаФорма.Элементы.ХТМЛ.Документ.parentWindow;
Если ОкноДок=Неопределено Тогда
ОкноДок=ЭтаФорма.Элементы.ХТМЛ.Документ.defaultView;
КонецЕсли;
Пока ОкноДок.queueHead<ОкноДок.queueTail Цикл //Если есть что-то в очереди
Данные=JSON_В_Объект(ОкноДок.getEvent(ОкноДок.queueHead+1));
ОкноДок.queueHead=ОкноДок.queueHead+1;
ВсегоСобытий=ВсегоСобытий+1;
Сообщить("X="+Данные.x+" Y="+Данные.y+" Всего событий="+ВсегоСобытий);
КонецЦикла;
КонецПроцедуры //
Преобразование полученных данных
&НаСервереБезКонтекста
Функция JSON_В_Объект(СтрокаJSON)
Чтение=новый ЧтениеJSON();
Чтение.УстановитьСтроку(СтрокаJSON);
Результат=ПрочитатьJSON(Чтение);
Чтение.Закрыть();
Возврат Результат;
КонецФункции //
Конечно же, рассмотренный механизм может быть использован не только в случае взаимодействия JavaScript и 1С. Его можно применять для асинхронного обмена данными между самыми разными процессами, но при соблюдении следующих требований: очередь должна быть постоянно доступна всем участникам обмена, и добавлять данные в очередь может только один участник.
К статье приложена обработка с вышеописанным примером.
PS: Почему-то не получилось добавить обработку-пример ни для бесплатного скачивания, ни для скачивания за 0 StartMoney. Печаль.