Guest User

Untitled

a guest
Sep 16th, 2026
15
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 28.49 KB | None | 0 0
  1. Техническое описание: тики, горутины персонажей, расписания и маршруты
  2.  
  3. 1. Назначение
  4.  
  5. Система должна поддерживать большое количество персонажей с разной степенью детализации симуляции:
  6.  
  7. • активные персонажи самостоятельно следят за глобальным счётчиком тиков и выполняют свою логику;
  8. • неактивные персонажи не обрабатывают каждый тик, а просыпаются по событиям и расписанию;
  9. • движение в выгруженных областях рассчитывается упрощённо;
  10. • при активации областей маршрут уточняется, а персонажи при необходимости переходят к подробной симуляции.
  11.  
  12. Каждому персонажу соответствует собственная горутина. Пул worker вместо горутин персонажей в этой архитектуре не используется.
  13.  
  14. 2. Основные сущности
  15.  
  16. СущностьОтветственность
  17. Координатор времениУвеличивает глобальный счётчик, управляет торможением
  18. Горутина персонажаВладеет состоянием персонажа, обрабатывает тики и события
  19. Планировщик расписанийОтправляет уведомления при наступлении заданного игрового тика
  20. Индекс маршрутов по зонамПозволяет найти маршруты, пересекающие активируемую область
  21. Кэш путейХранит повторно используемые данные пути и при необходимости его детализацию
  22. Точная пространственная сеткаХранит фактическое положение объектов, участвующих в подробной симуляции
  23. Игровое время выражается номером тика. Реальное монотонное время используется координатором для выдерживания частоты.
  24.  
  25. 3. Глобальные тики
  26.  
  27. 3.1. Счётчик времени
  28.  
  29. Глобальный счётчик:
  30.  
  31. • имеет тип atomic.Uint64;
  32. • изменяется только координатором;
  33. • увеличивается строго на единицу;
  34. • не пропускает значения;
  35. • доступен персонажам и системным горутинам для чтения.
  36.  
  37. Значение глобального счётчика означает последний выпущенный тик, который активные участники могут обрабатывать.
  38.  
  39. Остановка игры передаётся отдельным сигналом, например через context, а не специальным значением счётчика.
  40.  
  41. 3.2. Локальный прогресс
  42.  
  43. У каждого активного участника имеется локальный номер полностью завершённого тика.
  44.  
  45. Участник:
  46.  
  47. 1. Читает глобальный счётчик.
  48. 2. Если локальный счётчик меньше глобального, выполняет следующий локальный тик.
  49. 3. После полного завершения работы увеличивает локальный счётчик.
  50. 4. Публикует завершённый тик в атомарном поле.
  51. 5. Повторяет проверку.
  52.  
  53. Отставшие активные участники выполняют тики последовательно. Присваивать глобальное значение локальному счётчику без выполнения работы нельзя.
  54.  
  55. Исключение — переход из упрощённой симуляции в активную: он описан отдельно.
  56.  
  57. 3.3. Допустимая рассинхронизация
  58.  
  59. Участники могут находиться на разных локальных тиках.
  60.  
  61. Большинство взаимодействий передаются событиями. Критические изменения состояния, например нанесение урона, используют необходимую синхронизацию.
  62.  
  63. Блокировки обеспечивают безопасность изменения данных, но сами по себе не обеспечивают единый хронологический порядок событий.
  64.  
  65. 4. Координатор времени
  66.  
  67. 4.1. Частота проверок
  68.  
  69. Координатор работает отдельной горутиной с целевой частотой 10 проверок за номинальную длительность игрового тика.
  70.  
  71. checkInterval = tickDuration / 10
  72.  
  73. Пример для 60 Гц:
  74.  
  75. Длительность тика: ≈ 16,67 мс
  76. Целевой интервал проверки: ≈ 1,67 мс
  77.  
  78. Эта частота является целевой, а не гарантированной: планировщик Go и ОС могут задерживать выполнение.
  79.  
  80. 4.2. Подстройка сна
  81.  
  82. Координатор измеряет фактические интервалы между проверками и динамически подбирает сон:
  83.  
  84. • учитывает время собственной работы;
  85. • при устойчивых опозданиях уменьшает сон;
  86. • использует сглаживание измерений, чтобы не реагировать резко на единичные задержки;
  87. • имеет настраиваемую нижнюю границу сна, исключающую постоянное активное ожидание.
  88.  
  89. Подстройка не должна отнимать у персонажей CPU ради формального достижения частоты проверок.
  90.  
  91. Во время торможения координатор продолжает работать с той же целевой частотой.
  92.  
  93. 4.3. Итерация координатора
  94.  
  95. На каждой итерации координатор:
  96.  
  97. 1. Принимает доступные запросы торможения и снятия регистрации.
  98. 2. Проверяет прогресс зарегистрированных отстающих.
  99. 3. Удаляет завершённые запросы.
  100. 4. При отсутствии отстающих и наступлении срока выпускает следующий тик.
  101. 5. Рассчитывает время следующего пробуждения.
  102.  
  103. Обработка входящих сообщений должна быть ограничена по количеству или времени, чтобы непрерывный поток запросов не вытеснял остальную работу.
  104.  
  105. 5. Торможение глобального времени
  106.  
  107. 5.1. Регистрация отставания
  108.  
  109. Порог отставания первоначально принимается равным 10 тикам и задаётся параметром.
  110.  
  111. Если:
  112.  
  113. globalTick - completedTick > maxLagTicks
  114.  
  115. участник отправляет координатору указатель на структуру своего прогресса:
  116.  
  117. type ParticipantProgress struct {
  118. ID ParticipantID
  119. CompletedTick atomic.Uint64
  120. }
  121.  
  122. Идентификаторы системных участников, включая планировщик, не должны конфликтовать с идентификаторами персонажей.
  123.  
  124. Правила:
  125.  
  126. • структура имеет стабильный адрес;
  127. • после начала использования содержащий atomic объект не копируется;
  128. • участник записывает прогресс через Store;
  129. • координатор читает через Load;
  130. • канал регистрации не заменяет атомарный доступ к последующим обновлениям.
  131.  
  132. В первой реализации прогресс публикуется после каждого завершённого активного тика.
  133.  
  134. 5.2. Набор отстающих
  135.  
  136. Координатор единолично владеет набором зарегистрированных отстающих. Mutex для этого набора не нужен.
  137.  
  138. При получении запроса координатор:
  139.  
  140. 1. Проверяет актуальный прогресс.
  141. 2. Если участник уже догнал, не добавляет его.
  142. 3. Иначе добавляет или обновляет его регистрацию.
  143.  
  144. Повторные запросы одного участника объединяются. Участник не отправляет запрос на каждом догоняющем тике: достаточно одного запроса на эпизод отставания.
  145.  
  146. 5.3. Поведение при торможении
  147.  
  148. Пока набор отстающих не пуст:
  149.  
  150. • глобальный счётчик не увеличивается;
  151. • персонажи продолжают догонять текущий глобальный тик;
  152. • координатор продолжает принимать сообщения и проверять прогресс.
  153.  
  154. Участник считается догнавшим, если:
  155.  
  156. completedTick >= globalTick
  157.  
  158. Один догнавший участник не снимает торможение за остальных.
  159.  
  160. 5.4. Возобновление времени
  161.  
  162. Когда набор становится пустым:
  163.  
  164. 1. Координатор выпускает один следующий тик сразу.
  165. 2. Следующий выпуск назначается через обычную длительность тика.
  166.  
  167. Реальное время, прошедшее в торможении, не создаёт задолженность игровых тиков.
  168.  
  169. 5.5. Ограничения протокола
  170.  
  171. Порог отставания является мягким: до обработки запроса координатором глобальный счётчик может продвинуться дальше.
  172.  
  173. Запрос торможения нельзя молча потерять при переполнении канала.
  174.  
  175. При завершении участника или переходе в неактивный режим его регистрация должна сниматься отдельной командой. Запросы регистрации и снятия регистрации от одного участника должны обрабатываться в порядке их отправки.
  176.  
  177. При повторном использовании ID нужен номер поколения участника, чтобы старая команда не затронула новую горутину.
  178.  
  179. 6. Режимы персонажа
  180.  
  181. 6.1. Активный режим
  182.  
  183. Персонаж:
  184.  
  185. • следит за глобальными тиками;
  186. • выполняет каждый необходимый локальный тик;
  187. • обрабатывает входящие события;
  188. • при отставании участвует в механизме торможения.
  189.  
  190. Когда персонаж догнал глобальное время, он ожидает примерно четверть номинального тика перед повторной проверкой.
  191.  
  192. Ожидание должно прерываться входящим сообщением и остановкой игры. Реализация может использовать таймер совместно с select, а не безусловный time.Sleep.
  193.  
  194. 6.2. Неактивный режим
  195.  
  196. Персонаж:
  197.  
  198. • не проверяет глобальный счётчик периодически;
  199. • ждёт сообщения в своём inbox;
  200. • выполняет пункты расписания;
  201. • может обрабатывать внешние события;
  202. • не считается отставшим из-за длительного сна.
  203.  
  204. Для неактивных персонажей задержка обработки расписания на несколько секунд допустима.
  205.  
  206. Само пробуждение не обязательно означает переход в активный режим: персонаж может выполнить пункт расписания, зарегистрировать следующий и снова уснуть.
  207.  
  208. 6.3. Переход в активный режим
  209.  
  210. При активации персонаж:
  211.  
  212. 1. Определяет актуальный игровой тик.
  213. 2. Актуализирует состояние по маршруту, расписанию и прошедшему времени.
  214. 3. Обрабатывает необходимые накопленные переходы.
  215. 4. Принимает выбранный тик актуализации за исходную точку активной симуляции.
  216. 5. Начинает последовательно обрабатывать последующие тики.
  217.  
  218. Он не выполняет все тики, пропущенные во время штатного сна.
  219.  
  220. Если актуализация заняла время и глобальный счётчик уже продвинулся, дальнейшее отставание обрабатывается обычным механизмом активного режима.
  221.  
  222. 7. Планировщик расписаний
  223.  
  224. 7.1. Структура хранения
  225.  
  226. Основная структура:
  227.  
  228. map[Tick][]ObjectID
  229.  
  230. Ключ — целевой тик пробуждения. Значение — ID персонажей, запросивших уведомление на этот тик.
  231.  
  232. Содержимое расписания хранится у персонажа. Планировщик сообщает только:
  233.  
  234. «Наступило время проверить расписание».
  235.  
  236. 7.2. Регистрация
  237.  
  238. Регистрация выполняется через потокобезопасную функцию с mutex.
  239.  
  240. Под одним mutex должны быть согласованы:
  241.  
  242. • добавление записей;
  243. • извлечение записей для наступившего тика;
  244. • состояние обработанного диапазона времени;
  245. • обработка регистрации на уже пройденный тик.
  246.  
  247. Если целевой тик уже обработан, запрос направляется на ближайшую доставку, а не помещается в оставшийся позади элемент карты.
  248.  
  249. Отправка в inbox персонажа не выполняется под mutex расписания. Планировщик сначала забирает записи в локальный список, затем освобождает блокировку.
  250.  
  251. 7.3. Продвижение планировщика
  252.  
  253. Планировщик следит за глобальным счётчиком и обрабатывает тики последовательно.
  254.  
  255. Если он увидел переход с 100 на 103, он обрабатывает:
  256.  
  257. 101 → 102 → 103
  258.  
  259. Сам глобальный счётчик значений не пропускает; планировщик мог лишь не наблюдать промежуточные изменения.
  260.  
  261. После передачи уведомлений соответствующего тика в предусмотренную очередь доставки планировщик публикует свой завершённый тик. Завершения работы персонажами он не ждёт.
  262.  
  263. При отставании планировщик регистрируется у координатора по тому же протоколу, что и активный персонаж.
  264.  
  265. 7.4. Устаревшие уведомления
  266.  
  267. Получение уведомления не является безусловной командой завершить старый маршрут.
  268.  
  269. Персонаж проверяет собственное актуальное расписание. Если старый пункт отменён или заменён, ненужное уведомление игнорируется.
  270.  
  271. Это позволяет начать с хранения только ObjectID, без обязательного EventID.
  272.  
  273. 8. Упрощённое движение в выгруженных областях
  274.  
  275. 8.1. Начало маршрута
  276.  
  277. Неактивный персонаж:
  278.  
  279. 1. Получает путь из кэша.
  280. 2. Определяет общую длительность движения.
  281. 3. Фиксирует начало и ожидаемое окончание.
  282. 4. Регистрирует маршрут в пространственном индексе затронутых зон.
  283. 5. Регистрирует пробуждение на окончание маршрута.
  284. 6. Переходит в ожидание.
  285.  
  286. Потиковое изменение положения не выполняется.
  287.  
  288. 8.2. Исходная детализация
  289.  
  290. Первоначально достаточно знать:
  291.  
  292. • путь или ссылку на него;
  293. • затронутые зоны;
  294. • начало движения;
  295. • общую длительность;
  296. • время окончания.
  297.  
  298. Время входа в каждую зону заранее рассчитывать необязательно.
  299.  
  300. При этом кэш должен содержать или позволять получить данные, необходимые для последующей детализации: геометрию, длины участков, параметры движения.
  301.  
  302. 9. Пространственный индекс маршрутов
  303.  
  304. Маршруты регистрируются в отдельной структуре, параллельной точной сетке шардов.
  305.  
  306. Логически:
  307.  
  308. ZoneKey → набор ссылок на маршруты
  309.  
  310. Индекс не означает, что персонаж одновременно находится во всех пересекаемых зонах. Он позволяет найти маршруты-кандидаты при активации области.
  311.  
  312. Размер зоны задаётся параметром. В коде следует задавать длину стороны, чтобы исключить неоднозначность между 10 м² и 10×10 м.
  313.  
  314. Запись маршрута должна иметь идентификатор или поколение. Это необходимо, чтобы после отмены или замены пути отличать актуальные пространственные записи от старых.
  315.  
  316. Данные маршрута, читаемые несколькими горутинами, должны быть неизменяемыми после публикации либо защищёнными синхронизацией.
  317.  
  318. 10. Уточнение маршрута при активации зоны
  319.  
  320. 10.1. Условие детализации
  321.  
  322. Когда зона становится активной, система получает зарегистрированные в ней маршруты.
  323.  
  324. Если у маршрута отсутствует временная разметка по зонам, запускается её расчёт.
  325.  
  326. Одновременная активация нескольких зон одного маршрута не должна запускать несколько независимых одинаковых расчётов. Запросы объединяются либо обрабатываются последовательно горутиной персонажа.
  327.  
  328. 10.2. Результат детализации
  329.  
  330. В первой реализации рассчитываются интервалы прохождения всех зон маршрута:
  331.  
  332. type ZonePassage struct {
  333. Zone ZoneKey
  334. EnterTick Tick
  335. ExitTick Tick
  336. }
  337.  
  338. Интервалы рекомендуется трактовать как [EnterTick, ExitTick), чтобы однозначно обрабатывать границы.
  339.  
  340. Если маршрут возвращается в одну зону, для неё сохраняются несколько прохождений.
  341.  
  342. Разметка должна учитывать фактическое распределение времени движения. Делить общую длительность поровну между зонами нельзя, если это не соответствует модели маршрута.
  343.  
  344. Время начала и окончания уточнённого маршрута должно оставаться согласованным с исходным планом. Если оценка прибытия меняется, персонаж обновляет своё расписание.
  345.  
  346. 10.3. Действие после детализации
  347.  
  348. Для активной зоны определяется:
  349.  
  350. • персонаж уже прошёл её — немедленное пробуждение не нужно;
  351. • персонаж находится в ней сейчас — требуется актуализация положения;
  352. • персонаж войдёт позже — требуется пробуждение к моменту входа.
  353.  
  354. Нужно учитывать оба случая:
  355.  
  356. 1. Зона активировалась после регистрации маршрута.
  357. 2. Новый маршрут зарегистрирован через уже активную зону.
  358.  
  359. Проверять только событие первоначальной активации зоны недостаточно.
  360.  
  361. 10.4. Актуализация положения
  362.  
  363. Сообщение направляется горутине персонажа. Она:
  364.  
  365. 1. Проверяет актуальность маршрута.
  366. 2. Вычисляет положение на текущий игровой момент.
  367. 3. При необходимости включает точную пространственную представленность.
  368. 4. Выбирает дальнейший режим: подробное расписание или активная потиковая обработка.
  369.  
  370. Отмена, замена и завершение маршрута должны делать старые записи недействительными и обеспечивать их последующее удаление из индекса.
  371.  
  372. 11. Доставка сообщений
  373.  
  374. Для inbox персонажей и системных каналов необходимо определить ограниченную по памяти политику доставки.
  375.  
  376. Обязательные требования:
  377.  
  378. • координатор не зависает на отправке сообщения персонажу;
  379. • отправка не выполняется под пространственными блокировками или mutex расписания;
  380. • критические сообщения не теряются молча;
  381. • одинаковые уведомления «проверь актуальное состояние» допускается объединять;
  382. • завершение персонажа учитывается при доставке;
  383. • циклические блокирующие ожидания между персонажами исключаются.
  384.  
  385. В частности, участник, тормозящий мир, не должен ожидать наступления будущего глобального тика для завершения своего текущего тика.
  386.  
  387. 12. Проверки корректности и метрики
  388.  
  389. Обязательные тесты
  390.  
  391. 1. Глобальный счётчик увеличивается последовательно.
  392. 2. Локальный прогресс публикуется только после завершения тика.
  393. 3. Несколько отстающих независимо удерживают торможение.
  394. 4. Завершение одного участника не оставляет вечную регистрацию.
  395. 5. После торможения выпускается один тик без наверстывающего скачка.
  396. 6. Планировщик обрабатывает все наблюдённые с задержкой тики.
  397. 7. Регистрация на уже обработанный тик не теряется.
  398. 8. Длительный сон персонажа не вызывает ложного торможения.
  399. 9. Старое уведомление не завершает новый маршрут.
  400. 10. Учитывается будущий вход в уже активную зону.
  401. 11. Повторное пересечение одной зоны сохраняется в разметке.
  402. 12. Конкурентные сценарии проходят проверку go test -race.
  403.  
  404. Основные метрики
  405.  
  406. • фактические интервалы проверок координатора;
  407. • число отстающих и их отставание;
  408. • длительность и доля времени торможения;
  409. • задержка включения и снятия тормоза;
  410. • отставание планировщика;
  411. • размеры очередей;
  412. • количество активных и спящих персонажей;
  413. • число маршрутов и записей пространственного индекса;
  414. • стоимость и количество детализаций маршрутов.
  415.  
  416. 13. Границы текущего решения
  417.  
  418. В данное описание не входит очистка шардов при паузе. Торможение глобального счётчика не является безопасной остановкой всех операций мира.
  419.  
  420. Параметрами остаются:
  421.  
  422. • частота игровых тиков;
  423. • порог отставания;
  424. • минимальный сон координатора;
  425. • размеры каналов и очередей;
  426. • размер пространственных зон;
  427. • критерии активации и деактивации персонажей.
  428.  
  429. Основной принцип реализации: горутина персонажа остаётся владельцем его поведения; координатор управляет только игровым временем, планировщик — пробуждениями, а пространственный индекс маршрутов — обнаружением необходимости уточнить симуляцию.
Advertisement
Add Comment
Please, Sign In to add comment