Реализация
Первоначально TAS Editor базировался на коде экспериментального инструмента TASEdit из FCEUX 2.1.5.
TASEdit представлял из себя редактор Ввода (в духе TAS Movie Editor), встроенный в эмулятор, чтобы сократить время между редактированием и просмотром участков. Так как при разработке TASEdit не проводилось исследование принципов и закономерностей ТАСинга, у авторов TASEdit не было чёткого видения финального инструмента, и имеющийся код не был расширяемым. Поэтому вскоре после начала разработки TAS Editor его код был полностью переписан, чтобы облегчить дальнейшее расширение функционала.
Архитектура Тасэдитора спроектирована, исходя из авторских представлений о методическом ТАСинге и о необходимом функционале для облегчения такого ТАСинга.
Программные модули (классы)
taseditor.cpp
Main – главный шлюз между эмулятором и Тасэдитором (Main gate between emulator and Taseditor)
[единичный экземпляр]
- точка запуска Тасэдитора эмулятором
- точка выхода из Тасэдитора
- Регулярно (раз в кадр) вызывает обновление всех модулей, требующих регулярного обновления
- реализует функции раздела File: создание нового проекта, открытие файла, сохранение, компактное сохранение, импорт, экспорт
- обрабатывает некоторые хоткеи FCEUX
taseditor_window.cpp
Window – пользовательский интерфейс (User Interface)
[единичный экземпляр]
- реализует все операции с окном Тасэдитора: создание, перерисовка, изменение размера, перетаскивание окна, всплывающие подсказки, щелчки
- реализует обработчики для всех кнопок и чекбоксов в GUI Тасэдитора, чтобы запретить срабатывание клавиши Пробел, а также обрабатывать щелчок средней кнопкой мыши
- обрабатывает системные сообщения и передаёт сигналы от пользователя другим модулям Тасэдитора (а для некоторых простейших команд реализует логику на месте, например, диалог ввода настроек Greenzone capacity и т.п.)
- включает/выключает передачу клавиатурного управления эмулятору при получении/потере фокуса окна
- по требованию: обновляет текст заголовка окна; обновляет иконку курсора мыши
- обновляет состояние галочек при изменении соответствующих настроек
- хранит информацию о 10 последних проектах (File->Recent) и обновляет её при загрузках/сохранениях файлов
- Хранит ресурсы: текст заголовка окна, имя файла справки, размер, координаты и другие свойства всех элементов GUI
bookmarks.cpp
Bookmarks – Менеджер Закладок (Manager of Bookmarks)
[единичный экземпляр]
- хранит 10 Закладок
- реализует все операции с Закладками: ининициализация, установка Закладки, прыжок на Закладку, загрузка Ответвления Закладки
- сохраняет и загружает данные всех Закладок из файла проекта. При ошибке: сбрасывает список Закладок и Ответвлений
- реализует всю логику работы Списка Закладок: создание, перерисовка, наведение мышью, щелчки
- Регулярно обновляет состояние анимированных вспышек в Списке Закладок
- по требованию: обновляет расцветку строк Списка закладок, поддерживая актуальное состояние каждой ячейки Списка
- Хранит ресурсы: идентификатор сохранения, идентификаторы команд по работе с Закладками, текст заголовка панели, градиенты анимированных вспышек, номер слота по умолчанию
branches.cpp
Branches – Менеджер Ответвлений (Manager of Branches)
[единичный экземпляр]
- хранит информацию об Ответвлениях (родственных связах Закладок), а также номер текущего Ответвления
- дополнительно хранит время последнего изменения проекта (см. файерболл), а также время начала проекта (см. тучка)
- кеширует и хранит данные, использованные при рассчётах (кадр первого расхождения нпута для каждой пары Закладок; номер самой дальней Закладки, соответвтвующей хронологии определённой Закладки)
- сохраняет и загружает свои данные из файла проекта. При ошибке: передаёт сигнал выше
- реализует всю логику работы Дерева Ответвлений: создание, пересчёт связей, анимация, перерисовка, наведение мышью, щелчки
- по требованию: реагирует на изменение Закладок или текущего мувика, изменяя структуру Дерева Ответвлений
- Регулярно анимирует Дерево Ответвлений и рассчитывает положение Курсора Проигрывателя на этом Дереве
- Хранит ресурсы: координаты элементов Дерева Ответвлений, тайминги анимаций
bookmark.cpp
Bookmark – данные одной Закладки (Single Bookmark data)
- хранит всю информацию одной конкретной Закладки: снимок состояния мувика, копию сэйва одного кадра Гринзоны, скриншот этого кадра, состояние вспышки данной Закладки
- сохраняет и загружает свои данные из файла проекта. При ошибке: передаёт сигнал выше
- реализует процедуру установки Закладки: создание снимка мувика, установка ключевого кадра снимка на текущее положение Проигрывателя, копирование сэйва из Гринзоны, взятие и компрессия скриншота с экрана, запуск анимации вспышки
- запускает соответствующие вспышки при прыжке на Закладку и при загрузке Ответвления
snapshot.cpp
Snapshot – Моментальный снимок всех редактируемых данных (Snapshot of all edited data)
- хранит всю информацию одного снимка мувика: :Журнал Ввода, Журнал Лага на момент создания снимка, номер ключевого кадра, начальный и конечный кадр операции, тип операции и текстовое описание снимка (включая время создания)
- дополнительно хранит информацию о последовательной Записи/рисовании Ввода
- организует создание снимка: копирование Ввода из мувика, копирование Маркеров из Менеджера Маркеров, запоминание времени создания
- организует восстановление Маркеров из снимка
- сохраняет и загружает свои данные из файла проекта, при ошибке загрузки передаёт сигнал выше
inputlog.cpp
InputLog – Журнал Ввода (Log of Input)
- хранит снимок Ввода мувика: размер и содержимое Ввода для всех кадров (кнопочные нажатия и команды)
- опционально может хранить или не хранить карту Горячих Правок
- реализует копирование Ввода из мувика себе в Журнал, а также опциональное копирование карты Горячих Правок
- реализует детали полного/частичного восстановления данных из снимка: Ввода, карты Горячих Правок
- реализует компрессию и декомпрессию хранимых данных
- сохраняет и загружает свои данные из файла проекта, при ошибке загрузки передаёт сигнал выше
- реализует поиск первого несовпадения в содержимом двух снимков или в содержимом данного снимка и текущего состояния мувика
- предоставляет интерфейс для чтения отдельно взятых данных: чтение Ввода отдельного кадра, чтение любой ячейки карты горячих правок
- реализует все операции с картами Горячих Правок: копирование, частичное копирование, обновление/угасание, установка новых точек на карте горячих правок, сравнением Ввода двух снимков
laglog.cpp
LagLog – :Журнал лага (Log og Lag appearance)
- хранит данные о наличии/отсутствии лага для каждого кадра мувика
- реализует компрессию и декомпрессию хранимых данных
- сохраняет и загружает свои данные из файла проекта, при ошибке загрузки передаёт сигнал выше
- предоставляет интерфейс для чтения и записи в Журнал лага
markers.cpp
Markers – Моментальный снимок состояния маркеров (Snapshot of Markers state)
- хранит данные о состоянии Маркеров: массив распределения Маркеров по кадрам и массив Заметок
- реализует компрессию и декомпрессию хранимых данных
- сохраняет и загружает свои данные из файла проекта, при ошибке загрузки передаёт сигнал выше
- Хранит ресурсы: максимальная длина Заметки
popup_display.cpp
Popup display – Менеджер всплывающих окон (Manager of popup windows)
[единичный экземпляр]
- реализует все операции с всплывающими окнами: инициализацию, перерисовку, центрирование, декомпрессию и конвертирование скриншотов
- Регулярно отслеживает изменения Менеджера закладок и запускает/обновляет/закрывает всплывающие окна
- по требованию: обновляет содержимое всплывающих окон
- Хранит ресурсы: координаты и внешний вид всплывающих окон, тайминги всплывания/исчезания
history.cpp
History – История изменений мувика (History of movie modifications)
[единичный экземпляр]
- хранит массив записей в Журнал Истории (снимки мувика, резервные копии Закладок, резервные копии номера текущей Закладки) и указатель на текущий снимок
- сохраняет и загружает массив снимков из файла проекта, при ошибке загрузки очищает массив и начинает новую историю со снимка текущего состояния мувика
- по требованию: проверяет различия между последним снимком и текущим мувиком, принимает решение о необходимости создания новой точки отката. А в особых случаях может создать точку отката без проверки изменений, полагаясь, что вызывающий модуль сам проверил различия
- реализует все операции восстановления состояния мувика: откат (undo), повторение (redo), возврат на произвольный снимок из массива
- дополнительно хранит состояние курсора-указки
- Регулярно следит за обновлением состояние курсора-указки
- Регулярно (во время паузы эмулятора) ищет в Журнале несжатые пункты и сжимает первый попавшийся
- реализует всю логику работы Списка истории: создание, перерисовка, щелчки, автоскроллинг
- Хранит ресурсы: идентификатор сохранения, идентификаторы и текстовые обозначения всех возможных типов изменения мувика, тайминг указки
piano_roll.cpp
Piano Roll – интерфейс в виде перфоленты (Piano Roll interface)
[единичный экземпляр]
- реализует всю логику работы Списка Piano Roll: создание, перерисовка, скроллинг, наведение, щелчки, перетаскивание
- регулярно обновляет размер Списка в соответствии с размером Ввода мувика
- по требованию: скроллирует видимую область Списка к указанному элементу или позиции: к Курсору Проигрывателя, к Курсору Выделения, к курсору-указке, к целевому кадру Проигрывателя, к Маркеру
- сохраняет и загружает текущую позицию вертикального скроллинга из файла проекта, при ошибке загрузки скроллирует список в начало
- реализует все операции с Заголовком Списка: создание, перерисовка, анимация, наведение мышью, щелчки
- регулярно подсвечивает символы Заголовка в соответствии с текущими данными от Рекордера
- по требованию: запускает вспышки символов Заголовка
- реализует всю логику работы колеса мыши: скроллинг Списка, навигация Курсором Проигрывателя, навигация Курсором Выделения, пересечение пустот колесом
- реализует контекстное меню по правому щелчку
- Хранит ресурсы: идентификатор сохранения, идентификаторы колонок списка, ширины колонок списка, таблицы цветов ячеек, градиент карты Горячих Правок, градиент вспышек символов Заголовка, тайминги вспышек, все шрифты Тасэдитора, изображения
selection.cpp
Selection – Менеджер выделений (Manager of selections)
[единичный экземпляр]
- содержит определение типа данных "Набор выделенных кадров"
- хранит массив наборов выделенных кадров ("история выделений")
- сохраняет и загружает массив выделений из файла проекта, при ошибке загрузки очищает массив и начинает новую историю с пустого выделения
- постоянно отслеживает изменения выделенных строк в Списке и принимает решение о необходимости создания новой точки отката выделений
- реализует все операции восстановления состояния выделения: откат выделения (selection undo), повторение выделения (selection redo)
- по требованию: изменяет текущее выделение: очистить выделение, прыгнуть Курсором Выделения на указанный кадр, выделить регион, выделить всё, выделить между Маркерами, выделить содержимое Буфера Обмена
- Регулярно проверяет выход текущего выделения за рамки Списка и при необходимости корректирует текущее выделение, определяет факт перехода выделения на другой Маркер и обновляет Заметку в нижнем текстовом поле
- реализует логику работы нижних кнопок << и >> (прыжки Курсора Выделения по Маркерам)
- здесь же размещается код нижнего текстового поля для редактирования Заметок
- Хранит ресурсы: идентификатор сохранения, префикс подписи к нижнему полю
editor.cpp
Editor – Инструмент для редактирования (Tool for editing)
[единичный экземпляр]
- реализует операции изменения Ввода: установка/снятие Ввода на заданном участке, установка по шаблону, установка в Выделении, установка по шаблону в Выделении
- реализует операции изменения Маркеров: установка/снятие в Выделении, установка по шаблону в Выделении, установка всех в Выделении, снятие всех в Выделении
- Хранит данные Шаблонов и код их загрузки/генерации
- Хранит ресурсы: имя файла шаблонов, идентификатор нажатия кнопки в шаблонах
splicer.cpp
Splicer – Инструмент для монтажа (Tool for montage)
[единичный экземпляр]
- реализует все операции массового редактирования Ввода: копипаст, клонирование, очистка региона, вставка и удаление кадров, обрезка
- хранит данные о выделении, использованном при последнем копировании в Буфер Обмена
- Регулярно проверяет состояние текущего выделения и выводит информацию о нём на панель, также выводит информацию о данных в Буфере Обмена
- при запуске Тасэдитора проверяет содержимое Буфера Обмена
- Хранит ресурсы: символьные коды кнопок Ввода, текст для информационной панели Splicer
taseditor_config.cpp
Config – текущая конфигурация (Current settings)
[единичный экземпляр]
- хранит состояние всех настроек Тасэдитора
- все модули Тасэдитора могут обращаться к этому классу для получения или изменения текущих настроек
- при запуске FCEUX эмулятор записывает данные из файла fceux.cfg в этот класс, при выходе считывает данные из этого класса в файл fceux.cfg
- Хранит ресурсы: значения настроек по умолчанию, предельные значения настроек
playback.cpp
Playback – проигрыватель состояний эмулятора (Player of emulation states)
[единичный экземпляр]
- реализует функции проигрывателя мувика: показ любого кадра (прыжок), запуск/отмена добегания к указанному кадру, пауза, перемотка
- Регулярно следит за изменениями текущего кадра эмуляции (и управляет эмулятором), вызывает перерисовку соответствующих ячеек Списка, останавливает добегание при достижении цели, анимирует перерисовку кадра цели, обеспечивает следование Списка за курсором, определяет факт перехода курсора на другой маркер и обновляет текст в верхнем поле
- реализует логику работы верхних кнопок << и >> (прыжки курсора по маркерам)
- реализует логику работы кнопок < и > (покадровое перемещение курсора)
- реализует логику работы кнопки || (пауза) и средней кнопки мыши, а также реагирует на внешние изменения паузы эмуляции
- реализует все операции с прогрессбаром: сброс, установка значений, щелчки (отмена добегания)
- здесь же размещается код верхнего поля для редактирования Заметок
- Хранит ресурсы: префикс подписи к верхнему полю, тайминги анимации кадра цели, длительность задержки перед повторным срабатыванием зажатых кнопок GUI, масштаб прогрессбара
greenzone.cpp
Greenzone – зона доступа (Access zone)
[единичный экземпляр]
- хранит массив сэйвов, используемых для ускорения перехода Проигрывателя на любой указанный кадр
- также хранит журнал проявлений лага в кадрах
- сохраняет и загружает массив сэйвов и историю лага из файла проекта, при ошибке загрузки гринзона усекается до последнего успешно загруженного сэйва
- Регулярно проверяет наличие сэйва для текущего кадра в своём массиве, при отсутствии создаёт его, а также записывает информацию о проявлении лага в предыдущем кадре
- реализует авто-подгонку Ввода по лагу
- Регулярно производит постепенную очистку массива сэйвов для экономии памяти, удаляя самые старые сэйвы
- по требованию: (при изменении Ввода мувика) усекает размеры Гринзоны, удаляя сэйвы, ставшие неактуальными вследствие изменения Ввода. После усечения может также переместить Курсор Проигрывателя (который обязан всегда находиться внутри Гринзоны), а также запустить добегание к цели
- Хранит ресурсы: идентификатор сохранения, настройки постепенной очистки, тайминг очистки
recorder.cpp
Recorder – инструмент для записи Ввода (Tool for input recording)
[единичный экземпляр]
- в момент записи кнопочных нажатий в мувик (в самом конце кадра) по сигналу эмулятора перехватывает запись и накладывает свои фильтры мультитрекинга, а затем отражает изменения Ввода в Истории и Гринзоне
- Регулярно отслеживает нажатые кнопки виртуальных джойстиков и предоставляет данные для подсветки символов Заголовка Списка. Также реагирует на использование хоткея Switch Read Only и обновляет элементы интерфейса на панели Recorder и заголовок панели Закладок
- реализует функцию редактирования Ввода в режиме Read Only (ColumnSet кнопками виртуального джойстика)
- Хранит ресурсы: идентификаторы и текстовые обозначения режимов мультитрекинга, суффиксы для заголовка окна TAS Editor
markers_manager.cpp
Markers_manager – Менеджер Маркеров (Manager of Markers)
[единичный экземпляр]
- хранит один снимок состояния Маркеров, отвечающий за текущее (актуальное) состояние Маркеров в проекте
- сохраняет и загружает свои данные из файла проекта, при ошибке загрузки очищает данные
- Регулярно следит, чтобы размер массива Маркеров был не меньше количества кадров в мувике
- реализует все операции с Маркерами: установка Маркера на кадр, удаление Маркера, вставка и удаление кадров между Маркерами, усечение массива Маркеров, изменение Заметок, поиск кадра по номеру Маркера, интерфейс доступа к данным внутри снимка состояния Маркеров
- реализует копирование данных между снимками состояния Маркеров, а также поиск первого несовпадения в содержимом двух снимков состояния Маркеров
- здесь же размещается код поиска "похожих" Заметок
- здесь же размещается код редактирования Заметок к Маркерам
- здесь же размещается код диалога Find Note
- Хранит ресурсы: идентификатор сохранения, настройки поиска похожих Заметок
taseditor_lua.cpp
Lua – Менеджер возможностей Луа (Manager of Lua features)
[единичный экземпляр]
- реализует логику всех функций Lua-библиотеки taseditor
- содержит список отложенных изменений Ввода
- по требованию (от FCEUX Lua engine): обновляет кнопку "Run function"
- Хранит ресурсы: идентификаторы джойпадов для изменений Ввода, максимальная длина имени для функции applychanges(), текст для кнопки "Run function" по умолчанию
taseditor_project.cpp
Project – Менеджер рабочего проекта (Manager of working project)
[единичный экземпляр]
- хранит информацию о файловом имени проекта и о наличии несохранённых изменений
- реализует сохранение и загрузку проектов из файловой системы
- реализует функцию автоматического сохранения проекта
- Хранит ресурсы: масштаб настройки автосохранения, имя проекта по умолчанию
Модификация эмулятора
Тасэдитор требует внесения следующих модификаций в код самого эмулятора.
В модули Main/Window:
- вызывать центральную функцию Тасэдитора после эмуляции каждого кадра, а также, когда эмулятор на паузе. Частота вызова должна быть не менее 20 раз в секунду (это требуется для плавности анимаций)
- передавать системные сообщения окну Тасэдитора, в том числе сообщения клавиатурных акселераторов
- если эмулятор не использует колесо мыши, он должен пересылать WM_MOUSEWHEEL Тасэдитору, а не игнорировать это сообщение. То же самое с обработкой средней кнопки
- при выходе из эмулятора, если запущен Тасэдитор, необходимо вызывать функцию AskSave(), чтобы Тасэдитор проверил наличие несохранённых изменений и позволил пользователю сохранить их. Если функция AskSave() возвращает false, отменять выход из эмулятора (это означает, что пользователь выбрал Отмену)
В модуль, ответственный за Movie:
- предоставлять полный доступ к данным текущего мувика (создание/чтение/запись/любая модификация). Мувик должен быть промежуточным слоем между пользовательским вводом (генерируемым с помощью виртуальных джойстиков) и эмулируемой игрой. Игра ни на каком этапе эмуляции не должна брать Ввод напрямую из виртуальных джойстиков. Альтернатива (как реализовано в FCEUX): при любом изменении Ввода мувика на текущем кадре эмулятор должен изменять состояние виртуальных джойстиков, используя данные из мувика. В любом случае, Тасэдитор взаимодействует с игрой, читая и модифицируя данные мувика, и не опрашивает виртуальные джойстики. В Piano Roll отображаются данные из текущего мувика, и при редактировании Ввода изменяются данные текущего мувика
- в режиме Записи в начале каждого кадра (сразу после чтения данных из виртуальных джойстиков в мувик) отправлять сигнал Рекордеру (и Рекордер может изменить данные этого кадра в мувике)
В модуль, ответственный за Input:
- предоставлять интерфейс для чтения нажатых в данный момент кнопок виртуального джойстика
- Сброс/выключение и другие команды эмулируемой платформы не должны срабатывать сразу по команде пользователя. Они должны работать подобно копкам виртуальных джойстиков, чтобы у Тасэдитора была возможность разрешить или запретить выполнение вызванной команды. А когда режим Записи отключен, у пользователя вообще не должно быть возможности вызвать команды эмулируемой платформы
В модуль, ответственный за Output:
- предоставлять доступ к текущему значению флага лага (в конце каждого кадра)
- иметь функцию записи текущего скриншота в память (в том числе с наложенным HUD и Lua-вывода)
В модуль, ответственный за SaveStates:
- иметь функцию создания сэйва в памяти и функцию загрузки состояния игры из сэйва в памяти
- сэйвы должны восстанавливать состояние игры в точности
- сохранение и загрузка сэйва не должны занимать много процессорного времени, так как Гринзона автоматически создаёт сэйв для каждого кадра, и это не должно быть заметным для пользователя
- сэйвы должны храниться в сжатом виде, чтобы объём сэйва не занимал слишком много места, так как для комфортной работы в Тасэдиторе требуется хранить Гринзону как минимум для 1000 ближайших кадров
- в сэйвах Гринзоны не должно быть данных текущего мувика (это лишняя трата места)
В модуль, ответственный за Config:
- при запуске эмулятор должен загружать данные taseditor_config из файла общих настроек эмулятора, если же файл не найден, не трогать taseditor_config (тогда в нём останутся настроки по умолчанию)
- при выходе эмулятор должен сохранять данные taseditor_config в файл общих настроек
В модуль, ответственный за Lua engine:
- поддерживать библиотеку taseditor. Логика функций реализуется самим Тасэдитором, а от эмулятора требуется брать входные данные из стека и передавать их соответствующей функции Тасэдитора, а затем принимать выходные данные и помещать их в стек Луа
- поддерживать регистрацию не более одной Manual-функции и как минимум одной Автофункции
- подавать сигнал Тасэдитору об изменении статуса Manual-функции (регистрация/удаление), чтобы тот изменял состояние кнопки "Run function"
В модуль, ответственный за Replay:
- эмулятор должен уметь проигрывать проекты Тасэдитора как обычные мувики, игнорируя дополнительные данные в конце файла
- так как размеры проектов Тасэдитора могут быть огромными, при открытии файла не нужно загружать их в память полностью
- определять тип проигрываемого мувика (обычный мувик или мувик с данными Тасэдитора в конце). Если это проект Тасэдитора, то при попытке редактирования его содержимого (при загрузке сэйва в режиме Read+Write) эмулятор должен отказываться от редактирования и предлагать запуск Тасэдитора, а в случае запуска сразу передавать Тасэдитору ссылку на проигрываемый файл
Остальное:
- эмулятор обязан быть стабильным и детерминированным. Основой работы Проигрывателя Тасэдитора является строгая детерминированность игрового процесса. Если при загрузках сэйвов состояние игры будет хоть немного отличаться от состояния в момент сохранения, это повлечёт много проблем, в частности, навигация Курсором Проигрывателя будет неадекватной
- необходимо учесть все пункты Защиты от ошибок, относящиеся к модификации эмулятора. В частности при запуске Тасэдитор должен иметь возможность временно отключать ряд настроек эмулятора и восстанавливать их значение при выходе. Эмулятор должен обеспечить невозможность редактирования пользователем этих настроек во время работы Тасэдитора
- желательна высокая производительность эмуляции, позволяющая ускорять эмуляцию (функция "турбо") во много раз. Неплохо также иметь возможность автоматически отключать звук при турбо-эмуляции.
Created with the Personal Edition of HelpNDoc: Single source CHM, PDF, DOC and HTML Help creation