Зачем Python в нашем моде#
Python работает отдельным процессом: принимает сообщения, проверяет намерения, ведёт сетевую сессию и организует доставку. Игра исполняет свои Lua/MD и нативные операции. Поэтому Python-функция с названием request обычно ещё не является действием над кораблём.
Эта статья продолжает разбор переименования. Небольшие примеры языка ниже можно читать отдельно; они не соединяются с игрой. В реальном файле object_names.py запись местами плотная. Допустимо мысленно раскладывать её на несколько строк, сохраняя порядок.
Функция, аргумент и отступ#
def display_name(raw):
if raw == "":
return "Стандартное имя"
return raw
name = display_name("Север")def объявляет функцию. raw — входной аргумент. Двоеточие начинает блок, отступ показывает, какие строки к нему относятся. return заканчивает вызов и возвращает значение. Здесь name содержит Север.
В Python нельзя свободно убирать отступы, как пробелы в XML. Строка после if с большим отступом выполняется только в его ветке. Конец блока определяется возвращением к прежнему уровню отступа.
Словарь — запись с именованными полями#
request = {
"request_id": "lesson-1",
"object": "ship-example",
"revision": 1,
"name": "Север",
}
object_key = request["object"]
scope = request.get("scope", "object")Здесь {...} создаёт словарь, ключ: значение добавляет поле. request["object"] требует наличие ключа. get позволяет задать запасное значение. В примере поле scope не задано, поэтому получается object.
Значения учебные, это не пакет для ручной отправки хосту. Настоящая команда дополнительно имеет контекст, соединение и проходит допуск.
Другие контейнеры: [] — список, () часто обозначает кортеж, set() — пустое множество. Запись {"host", "guest"} создаёт множество, а {} — пустой словарь. Как и в Lua/MD, вид скобок нужно читать в контексте языка.
Основные операторы в файлах проекта#
| Запись | Значение |
|---|---|
= |
Присвоить |
==, != |
Проверить равенство / неравенство |
<, <=, >, >= |
Сравнить |
and, or, not |
Логические операции с коротким вычислением |
in, not in |
Есть / нет в контейнере; у словаря проверяются ключи |
is None, is not None |
Проверить отсутствие / наличие значения по соглашению Python |
+, -, *, /, % |
Арифметика; для некоторых типов смысл другой |
+= |
Изменить значение, например revision += 1 |
obj.field |
Доступ к атрибуту объекта |
raw["name"] |
Доступ по ключу |
# пояснение |
Комментарий |
В Python пустые строки, списки, словари и нулевые числа ложны в условии; в Lua 0 и пустая строка истинны. Не переводи условия механически между языками.
Разбираем плотную строку из проверки намерения#
В intent(raw) есть выражение:
set(raw) - {"scope"} != {"request_id", "object", "revision", "name"}После проверки, что raw — словарь, set(raw) получает множество его ключей. Вычитание убирает необязательный scope. Оставшийся набор должен точно совпасть с обязательными полями. Поэтому отсутствующее обязательное поле и неожиданное лишнее поле приводят к отказу.
Проверка ключей ещё не проверяет значения. Следом вызываются проверки token, integer, name_text. Это собственные функции проекта: найди их определения через rg -n, прежде чем приписывать им поведение стандартного Python.
Класс и self#
class PendingName:
def __init__(self):
self.pending = None
def begin(self, name):
self.pending = name
operation = PendingName()
operation.begin("Север")Это учебный класс, его нет в API проекта. Класс описывает объект с данными и методами. __init__ задаёт начальное состояние. self — конкретный экземпляр. После вызова operation.pending содержит Север.
У реального ObjectNameAuthority в self находятся states, records, pending, responses, acks и другие поля. Значит, self.pending относится к данной подсистеме, а session.object_names позволяет получить эту подсистему из общей сессии.
Не путай такие поля с локальной переменной метода: локальное result живёт в вызове, а self.responses используется между вызовами.
Почему работа разделена на request, pump и observe#
Метод в ObjectNameAuthority |
Когда нужен | Что делает |
|---|---|---|
request |
Пришло намерение | Проверить и записать ожидающую операцию либо ответ |
pump |
Очередной проход обслуживания | Начать загрузку или отправить/повторить команду движку |
observe |
Пришли данные из X4 хоста | Проверить наблюдение и обновить состояние/исход |
stream |
Собирается снимок участнику | Отобрать разрешённое состояние в пределах бюджета |
acknowledge |
Пришёл ACK | Учесть применение конкретной ревизии |
Это имена и обязанности данного семейства. У соседних подсистем контракт может отличаться.
Разделение позволяет быстро закончить обработку запроса и продолжить работу позже, когда движок ответит. Занятый цикл while, ожидающий ответ в UI, заморозил бы интерфейс. Здесь ожидание представлено данными, а не удержанием вызова.
Ранний выход и исключение#
В исходниках часто встречается форма:
if not self.ready:
returnЧитай как «продолжать пока рано». return без значения даёт None. В request также встречаются return True и return False. Смысл этих флагов определяется вызывающим кодом. В этом семействе True не равен успешному переименованию: метод может вернуть True, сохранив ответ denied. Реальный исход находится в status и подтверждённых данных.
raise ProtocolError(...) создаёт исключение проекта. try/except позволяет вызывающей стороне обработать ожидаемую ошибку. Найди обработку, прежде чем решать, что такой отказ обрушает всю службу или, наоборот, всегда безопасно игнорируется.
Полезные встроенные операции#
| Вызов | Что делает |
|---|---|
isinstance(raw, dict) |
Проверяет тип |
len(rows) |
Количество элементов |
dict(...) |
Создаёт словарь; форма с аргументами задаёт поля |
set(raw) |
Создаёт множество, у словаря — из ключей |
sorted(keys) |
Возвращает отсортированный список |
value.encode("utf-8") |
Превращает строку в байты UTF-8 |
wire.split("|") |
Разделяет строку по символу |
"|".join(parts) |
Собирает строковые части с разделителем |
Из len(name) и len(name.encode('utf-8')) получаются разные ограничения. Например, кириллица занимает больше одного байта на букву в UTF-8. В object_names.py проверяется и текст, и его допустимый размер; не своди оба условия к одной произвольной длине.
Документация языка для дальнейшего чтения: управление потоком, структуры данных, классы. Эти основы полезны до изучения всего сетевого стека.
Что читать в C++ пока достаточно#
На первом проходе узнавай #include, функцию, аргументы, return, if, указатели * и обращения ./->. Нативный слой требует отдельного знания типов и ABI — соглашений, по которым взаимодействуют скомпилированные части.
extern "C" у luaopen_x4fo нужен для точки входа C-модуля с ожидаемым именем. ffi.cdef в Lua описывает C-интерфейс, но не реализует его и не подтверждает наличие функции. Подробный разбор смещений памяти не нужен для первого знакомства с переименованием.
Теперь переходи к первой самостоятельной задаче или вернись на старт.