\chapter{Opal для программиста}

Или как написать собственную модель для использования с Opal.

\section{Краткий обзор архитектуры Opal}

Система Opal представлена в двух редакциях: локальная (local) и сетевая (net). В локальной редакции приложение сервера совмещено с клиентским приложением графического интерфейса. Оба варианта могут работать на одной машине, но только сетевой вариант способен работать раздельно с графическим интерфейсом. При таком подходе появляется больше свободы в управлении для сервера и пропадает нагрузка на клиентский компьютер (если, конечно, части разнесены по разным машинам в сети).

Локальная редакция, серверная часть совмещена с графической:

Сетевая редакция (сплошными линиями показаны фактические связи между компонентами, пунктирными — логические):

Если при использовании локальной версии нужно очень аккуратно использовать системные ресурсы и процессорное время, дабы избежать дискомфорта при работе с другими приложениями, то для сетевой редакции такой недостаток почти не имеет значения. Сервер может находиться на выделенной мощной машине, а при должной настройке задачи могут даже запускаться на кластере из многих компьютеров. Все это скрыто от конечного пользователя, предоставляя ему только управление задачами и моделями.

\section{Подключение своего приложения}

Вне зависимости от того какую редакцию использовать, все задачи находятся на той же машине, что и сервер. Сервер связывается с задачами через стандартные потоки ввода-вывода (stdin и stdout). Это сделано для того, чтобы упростить разработку пользовательских задач. Можно было бы, конечно, использовать подключения по протоколу TCP\slash{}IP, но это бы только вызвало лишние сложности, так как работа с этим сетевым протоколом зависит от операционной системы, и в многих языках программирования не содержится нужных библиотек в стандартной поставке.

Рассмотрим все этапы подготовки своего приложения и подключения его к системе Опал на основе данного в первой части условия задачи (стр. \pageref{testtask}). Исполняемый файл задачи будет называться testt.exe.

При старте сервер опрашивает задачи, путь к которым указан в специальном файле
\begin{verbatim}
tasks.conf
\end{verbatim}
Путь может быть как абсолютным, так и относительным. При использовании относительного пути следует помнить, что он вычисляется относительно исполняемого файла ссервера Opal. 

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

Чтобы добавить свой проект в систему, достаточно добавить в конец файла tasks.conf путь к исполняемому файлу задачи. 

Во время опроса система запускает задачу с ключом \emph{-i}:
\begin{verbatim}
reltask.exe -i
\end{verbatim}
а при работе будет запускать с ключом \emph{-r}:
\begin{verbatim}
reltask.exe -r
\end{verbatim}
так что исполняемый файл должен адекватно реагировать как минимум на эти два ключа. В то же время не рекомендуется использование каких либо других ключей запуска, а все настройки передавать через сообщения по протоколу Opal. В то же время такой подход удобен тем, что при старте без ключей (к примеру) может запускаться обычное отдельное приложение с оконным интерфейсом, а значит задача будет вести себя двояко: и как приложение для исползования с системой Opal, так и для использования как независимая программа.

\section{Ключи запуска}

Ключ \emph{-i} нужен для того, чтобы сервер получил полные сведения о задаче. Эти сведения спрашиваются один раз и используются в дальнейшем при построении таблицы параметров на графическом клиенте, проверке целостности данных, в качестве рекомендаций при управлении очередью задач.

После того как параметры опрошены, задача будет готова к запуску сервером. Когда клиент сделает запрос на запуск задачи, сервер запустит ее с ключом -r и через стандартный поток ввода передаст полученные от клиента параметры старта задачи (параметры задачи, модели и метода) и будет ожидать результата вычислений в виде таблицы данных.

\section{Протокол Opal}

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

Протокол обмена данными между всеми частями системы Opal базируется на формате обмена данными JSON (JavaScript Object Notation). Формат очень простой и построен на основе словарей и списков. Эти структуры данных если в большинстве современных языков программирования, а в некоторых JSON даже поддерживается стандартной библиотекой (к примеру, Python или JavaScript). Кроме того формат удобен для чтения и изменения человеком, что явилось немаловажным фактором при его выборе. Список всех полей можно прочитать в приложении №.

\section{Коммуникация сежду сервером и задачей}

Запустив задачу и передав ей параметры, сервер переходит в режим прослушивания. В этом режиме сервер больше не передает никаких сообщений задаче, а только принимает сообщения от нее и обрабатывает их. Сообщения содержат в себе информацию о ходе выполнения задачи, каких-либо комментариях, предупреждениях или ошибках, которые могли возникнуть во время выполнения, а также о результатах выполнения.

Как и в любом клиент-серверном приложении клиент посылает запросы серверу, а сервер на них отвечает. Система Opal использует тот же принцип. Графический клиент посылает запросы серверу, а сервер, в свою очередь, посылает запросы задачам. В любом случае каждый запрос должен иметь поле запроса, по котороу и будет определено, что же требуется выполнить. Текст запроса в общем случае выглядит так:
\begin{verbatim}
{ "request": %request-text% }
\end{verbatim}
где \%request-text\% — одна из препопределенных команд. Список таких команд представлен ниже:

todo заполнить полностью

get-task-list
	Получить список доступных для запуска задач.
	run-task
	Запустить новую задачу.
	stop-task
	Остановить задачу.
	get-status
	Узнать статус выполнения задачи.
	
В ответ на запрос сервер высылает сообщение, в котором должно присутствовать поле answer:
\begin{verbatim}
{ "answer": %answer-text% }
\end{verbatim}
где \%answer-text\% --- один из предопределенных ответов. Ответы характеризуют результат выполнения запроса и они должны быть сигналами для дальнейшей обработки.

ok
	Все прошло хорошо, можно анализировать возвращенные значения.
	warning
	Запрос выполнен, но при его выполнении возникли некоторые проблемы, подробнее о которых можно узнать, проанализировав полученные данные.
	error
	Запрос не выполнен, произошла критическая ошибка. Следует выяснить причину ошибки, если нужно обратиться к справочному руководству или в техническую поддержку.
	
	Кроме поля answer в ответе могут присутствовать поля code, value и comment. Эти поля используются для получения дополнительной информации. Так поле code может содержать числовой код ошибки, которая вызвала отказ или ошибку, поле value используется для возвращения некоторого значения (прогресс выполнения, список доступных задач и др.), comment сожержит произвольную текстовую строку, которая должна просто прояснить ситуацию где необходимо.
	
Пример запроса и ответа. В качестве простого запроса-ответа можно привести запрос статуса выполнения задачи. Графический клиент посылает серверу запрос
\begin{verbatim}
{   
    "request": "get-status", 
    "uid": %user-id%, 
    "tid": %task-id%
}
\end{verbatim}
где \%user-id\% и \%task-id\% обозначают уникальные идентификаторы клиента и задачи соответственно. О том, как они формируются и какую роль играют можно прочитать в последней части данного сочинения.
Сервер на основе uid и tid перенаправляет запрос нужной задаче, если, конечно, в данный момент она способна его обработать. После чего высылает клиенту результат разпроса (к примеру):
\begin{verbatim}
{
    "answer": "ok",
    "code": 0,
    "value": 0.6321,
    "comment": "in progress, all right"
}
\end{verbatim}
Если, например, клиент ошибся в tid или пытается получить доступ к чужой задаче, сервер даст ответ
\begin{verbatim}
{
    "answer": "error",
    "comment": "access denied"
}
\end{verbatim}
        
\section{Описание данных}

Перед тем как перейти к описанию структуры, описывающей зачаду и ее модели, следует рассказать о типах данных, которые применяются в Opal. Далее будем говорить только о тех полях, которые встречаются в задачах, моделях и методах.

Тип --- очень важная часть описания данных. Хотя без него можно было бы обойтись, предоставив пользователям полный контроль над передаваемыми данными, этого лучше избежать. Все дело в том, что тип является мощным инструментом контроля целостности передаваемых данных, не позволяя использовать, скажем, строку там, где должно быть целое число.

В описании поля используется следующий синтаксис:
        "name": "type [choice list] [default value] [title title]
[// comment-text]"

Подробнее о том как происходит разбор данного выражения можно прочитать в приложении №, подробные примеры есть в последнем пункте этой главы.

Из описания видно, что описания данных представляет собой словарь (dictionary, map, ассоциативный массив), где ключом является имя поля, а значением — его описание.

Таблица типов данных:
Тип
	Название
	Пример
	bool
	Логический
	true, false
	int
	Целочисленный
	0, 1, 100, -2, 897462
	float
	Действительный
	1.231e22, -34.4332
	string
	Строковый
	‘hello, world!’, ‘foo bar’
	time
	Момент времени
	‘2012-01-01 12:01:01’, ‘1:2:3’
	list
	Списочный
	[1, 2, 3], [‘a’, ‘b’, ‘c’]
	range
	Разбиение
	[0,10,20], [5,25], [1.2,1.5,0.3]
	period
	Период
	[‘0:0:0’, ‘12:0:0’]
	
\subsection{Логический тип \emph{bool}}
Значения: true, false
Логический тип один из основополагающих типов данных. Он может принимать всего два значения: истина и ложь. Отлично подходит для создания выключателей (или переключателей) дополнительных опций модели.
Присутствует в JSON.

\subsection{Целочисленный тип \emph{int}}
Значения: от -231 до 231-1 (4 байта)
Тип данных для хранения целых чисел со знаком. Пожалуй, еще более основной тип, чем логический. Если вы не собираетесь работать с большими целыми числами (к примеру, рассчитывать госдолг США) то представленного диапазона хватит для большинства задач.
Присутствует в JSON.

\subsection{Действительный тип}
float
Значения: от -1.7*10+308 до 1.7*10+308 (двойная точность, 8 байт)
Тип данных с плавающей точкой предназначен для хранения и обработки действительных чисел.
Присутствует в JSON.

\subsection{Строковый тип \emph{string}}
Строковый тип служит для хранения символов. Отсутствует разделение на строки и символы как во многих языках программирования. Это сделано для того, чтобы упростить работу с данными. Строки можно использовать в качестве комментариев. В качестве нетривиального примера можно привести использование строк вместе с оператором выбора в качестве перечисляемого типа (аналог enum):
        string choice [‘a’, ‘b’, ‘c’]
При записи строк в определении поля данных нужно быть внимательным с использованием кавычек. Кавычки внутри описания поля нужно экранировать символом обратного слеша \\. Лучше это будет видно на примере:
%"field": "string default \"foobar\""
Чтобы не запутаться надо просто помнить, что вы описываете строку в строке.
Присутствует в JSON.

\subsection{Тип момента времени \emph{time}}

Значения: от "1000-01-01 00:00:00" до "9999-12-31 23:59:59"
Является аналогом типа данных timestamp. При описании значений этого типа можно описывать как все поле целиком, так и использовать его части отдельно (время и дату). Отсутствующая часть будет заменена значением по умолчанию. Примеры:
        "12:10:00"
        "2012-01-01"
        "2012-02-03 13:23:43"
Не поддерживается JSON, представляется как строка.

\subsection{Списочный тип \emph{list}}

Списочный тип является единственным составным типом (все остальные типы — скалярные). Это значит, что при описании поля этого типа нужно указать элементы какого скалярного типа содержатся в списке. Синтаксис объявления:
        list (scalar)
Сразу хочется отметить, что вложенные списки не поддерживаются.
Списочный тип был введен для возможности передачи переменного числа аргументов и только. Если возникнет необходимость передать многомерный список, это можно сделать, использовав два списка: первый с размерностями и второй со значениями. Но такое на практике встречается крайне редко, а если вы встретитесь с таким случаем, возможно его можно преобразовать, чтобы он стал более простым.
Не рекомендуется употреблять оператор choice со списками. Это вызовет лишь лишнее нагромождение определения, а принесет минимум пользы. Как и в случае многомерных списков, наверняка, есть способ сделать все проще.
Примеры:
"field": "list(int) // простой список целых чисел"
"field": "list(int) default [1, 2, 3] // список целых чисел"
"field": "list(list(int)) // нельзя!"
"field": "list(int) choice [[1, 2], [3, 4]] // нагромождение!"
Присутствует в JSON.

\subsection{Тип разбиения \emph{range}}

Разбиение (диапазон) — нестандартный тип данных. Он основан на идее разбиения отрезка на одинаковые части. Это бывает очень полезно когда надо перебрать значения из некоторого диапазона с некоторым шагом.
Синтаксис записи значения типа выглядит следующим образом:
        [[begin,] end [, step]]
Если указан только параметр end, то считается, что диапазон начинается с 0. Шаг по умолчанию равен 1. Если вы используете этот тип данных в описании своих полей, следует определить соответствующую его обработку. Опущенные значения будут подставлены графическим приложением (если это поддерживается).
Не поддерживается JSON, представляется как список из трех значений.

\subsection{Выбор из нескольких вариантовsection}

Оператор сhoice предназначен для выбора одного варианта из предложенного списка. В записи определения поля после него должен идти список возможных значений:
choice [val1, val2, … ]
Примеры:
        "field": "int choice [1, 2, 3, 4]"
        "field": "string choice [‘a’, ‘b’, ‘c’]"
Каждое значение в списке должно соответствовать указанному типу поля. Не рекомендуется использовать оператор выбора вместе со списковым типом, хотя это и не возброняется.
Если в списке присутствует только одно значение или список пуст, то фактически выбор становится фиксированным. Не лишайте пользователей выбора.

\subsection{Значение по умолчанию}

Оператор default осуществляет подстановку значения по умолчанию в графическом клиенте. Нельзя переоценить важность этого оператора, так как значение по умолчанию помогает пользователям сориентироваться для выбора нужного им значения. Параметр по умолчанию — это некий средний параметр, который подходит в большинстве случаев и (или) рекомендуется к использованию. Золотое правило работы с такими параметрами: "Если не знаешь что делает этот параметр, используй значение по умолчанию".

Если вместе с оператором default используется оператор choice, то значение по умолчанию должно входить в список вариантов.
Синтаксис:
        default value
Примеры:
"field": "int default 10"
"field": "int choice [1, 2, 3] default 2"
"field": "string choice [‘foo’, ‘bar’, ‘foobar’]
default ‘foobar’"

\subsection{Заголовок поля}

Оператор title отвечает за заголовок поля. Текст, указанный как заголовок будет использоваться в графическом интерфейсе в качестве названия поля. Если заголовок не указан, будет использоваться имя поля.

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

\subsection{Комментарий}

Комментарии используются для того, чтобы пояснить для чего нужно описываемое поле. В графическом интерфейсе они проявляются как всплывающие подсказки. Чем яснее будет сформулированы комментарии, тем проще будет пользователю разобраться какие параметры и как нужно описывать для данной задачи.

\section{Задачи, модели и методы}

Покажем возможную структуру описания информационного сообщения и сообщения с параметрами, которое будет передано задаче, на примере, который был описан в первой главе про младшего сотрудника Василия.

Составление информационного сообщения — очень важный процесс при проектировании задачи. От того как описана задача напрямую зависит то, насколько удобно ей будет пользоваться и насколько гибко ей можно управлять.

Обратимся еще раз к тексту условия (страница №). Сперва нужно выделить те параметры, которые напрямую относятся к модели. В нашем случае это будут: энергия, температура в доме, количество работы, которую нужно выполнить. Можно заметить, что кроме этих граничных условий есть еще различные параметры, которые отвечают за изменение главных параметров с течением времени: изменение температуры в час, затраты энергии на выполнение работы, восполнение энергии за счет сна и питания. 
Эти параметры можно оставить постоянными, указав их как константы в коде задачи, но более грамотно будет вынести их в секцию метода, чтобы потом можно было сравнить поведение модели в зависимости от их изменения.

Следует заметить, что в модели нет метода по умолчанию. Хотя, если добавить Василию возможность ничего не делать, то это формально можно назвать методом без управления, но проблема в том, что если ничего не делать, задание не будет выполнено, а запас сил Васи вскоре кончится от холода.

Беря во внимание все вышеизложенное, такой результат может вернуть приложение-задача серверу на запрос информации (reltask -i):



Описание задачи состоит из списка моделей, которые включают в себя описание полей данных и методов. Методы тоже, как и модели, могут (и должны) включать в себя описание данных, которые будут использоваться управлением алгоритма.

В описании задачи, каждой модели, каждого метода могут присутствовать мета-секции title, description, data, results. Первые две отвечают за описание названия и описания соответственно. Данные могут быть описаны только в секции data и нигде более. results используется для описание результатов вычислений. Подробнее об этом будет рассказано в следующем параграфе.

\section{Результат вычислений}

\section{Обслуживающие запросы}
