Движок трекмо

Интересно Ваше мнение на такую тему как движок трекмо.
Я думаю, что движок должен делать следующее:
  • Отслеживать время перехода от части к части
  • Отрабатывать иниталайзы частей
  • Отработка декранча следующей части в процессе показа, при необходимости / возможности проца

Итак, по первому пункту нужно отслеживать время, отрабатываемое частью:
  • возможно, это количество halt для каждой части;
  • возможно — это отслеживание переключения позиции паттернов в плеере.
Что лучше — пока не знаю. Например — в запиляторе используется второй подход.

По иниталайзу — да, декранч / депак скрина и т.д. — всё что нужно приготовить перед стартом новой части.

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

Итак, вполне возможно что данные для движка должны выглядеть как-то так:

    dw end_time,part_code0,decrunch_code_part2 ;(либо 0 - если декранч не нужен)
    dw end_time,part_code1,0 
    dw end_time,part_code2,0 

    dw end_time1,part_code3,decrunch_code_part3
    dw end_time1,part_code4,0 

Т.е., при выборе первых значений записываем время выхода в цикл отсчёта, part_code_x в вызовы отображения (вполне возможно это всё использовать в инте), decrunch_code2 прописываем в основной цикл. в decrunch_code_х должны быть проверки на конец задачи с выходом.

Что я не учёл и какие рабочие примеры движка можно привести? Для размышлений и кода.
Прошу поделиться опытом и наработками, и давайте обсудим узкие моменты.

20 комментариев

avatar
В прошлом мае я написал универсальный движок для трекмо. Прототипом движка для меня было ядро написанное psndcj для дем tbk/4d; psndcj говорил, что его ядро основано на ядре эксполодера. Т.е. родословная, типа, о-го-го.

Немного о ядре psndcj . Оно очень шустрое и легковесное. Ядро сидит чуть ниже верхней страницы. Обработчик прерывания находится в ядре и является его ключевой частью. Прерывания разрешены ВСЕГДА. Обработчик считает фреймы и, при достижении различных ключевых моментов в треке, запускает пользовательские коды. Преимущество работы с номерами фреймов (в противоположность номерам паттернов, например) заключается в том, что
1. Это позволяет синхронизироваться не с точностью до паттерна, а точнее, вплоть до каких-то звучков, индивидуально.
2. Вообще говоря, очень часто музыка играется из PSG, т.е. музыка уже полностью потеряла информацию о номерах строк, паттернах и т.д., а синхронизироваться всё ещё нужно.
Кроме этого, в ядре находился декомпрессор, процедура быстрого копирования данных и процедуры для эффективной работы со скрытым экраном. Идея в том, что по окончанию отрисовки в скрытый экран, код может начинать рисовать следующий кадр, а собственно переключение экрана осуществляет само ядро, сразу после прерывания. Это позволяет более эффективно использовать машинные ресурсы.

Недостаток, с моей точки зрения, этого ядра заключался в том, что оно было написано для работы в стиле «распаковали эффект», «показали эффект», «расчиститили место для следующего эффекта». Ядро запускает эффект и пока эффект не закончился, ядро не контролирует процесс. Конечно, так писать в чём-то проще. Но это очень сильно усложняет фикс. Если нам, допустим хочется, что-то поменять в эффекте посередине эффекта, нам придётся внутри эффекта сравнивать счётчик фреймов ядра с какими-то константами, т.е., по сути, дублировать функциональность ядра.
Другим недостатком было то, что скрипт хранился в основной памяти (насколько я знаю, psndcj недавно сделал версию ядра в которой это ограничение было снято).

Женя psndcj , у меня понятно немного пристрастный взгляд на это всё, было бы классно если бы ты прокомментировал, м.б. расставил как-то иначе акценты. Кроме того, очень интересно узнать, что у тебя в ядре пришло в наследство от эксподера.
avatar
Что изменилось в моём ядре? Я поменял устройство скрипта. «Скрипт» у psndcj — это просто список слов, номера фреймов к которым привязаны переключения эффектов, адреса процедур которые нужно исполнить, иногда данные. Скрипт у меня — это примитивный байткод, который интерпретируется ядром. Я хотел чтобы весь фикс в деме осуществлялся на уровне ядра, а не внутри эффектов. Это означает, что эффекты никогда не получают полный контроль над машиной.

Какого рода команды есть у меня в скрипте? Самая главная команда: дождаться нужного фрейма:
kWAIT 3200
Некоторые команды чисто утилитарные: например, копирование и распаковка данных.
kCOPY PAGE0.PackedAndy, 23296, PAGE0.PackedAndyLen, 0
kUNPACK	23296, 49152, 7
(копируем упакованную картинку из 0й страницы вниз и потом рапаковываем её в экран на странице 7). Крайне, мегаполезные команды — kPOKE и kPPOKE:
kPOKE E01_ScrollerAttr, 201
kPPOKE	E01_AniSprite1, Phase1Sprite
(первая для байтов, вторая для слов). Когда нужно слегка модифицировать эффект для достижения какого-то фикса, нет ничего проще чем добавить простую заглушку (как в первом примере) или подменить адрес данных (как во втором).

Эффекты я обычно стараюсь писать в прерываниях. Обработчик ядра, помимо своих обыкновенных обязанностей, умеет запускать процедуру пользователя. Понятно, задача порубить эффект на фреймы не всегда удобно разрешима, но я стараюсь по возможности это делать, т.к. эффекты в прерываниях позволяют ядру работать параллельно с эффектами, что даёт некую ограниченную форму многозадачности. Типичный план фрейма таков: обработчик прерывания обновляет номер текущего фрейма, играет музыку, затем запускает процедуру пользователя, после чего контроль возвращается ядру. Скрипт может сказать ядру в этом случае ждать пока номер фрейма не достигнет чего-то, т.е. крутиться в холостом цикле до следующего прерывания. Но вполне возможен вариант, когда скрипт говорит ядру распаковать данные, и тогда получается, что каждый свободный такт, оставшийся во фрейме после эффекта, будет потрачен на распаковку. Вот пример достаточно нетривиального кода из Wonderball, для переключения со сцены 2 на сцену 3 (сцена три — это шас с радиальными лучами анимацией):
kWAIT 2304-4 : kPOKE E01_ScrollerAttr, 201 ; атрибуты скроллера выключается немного заранее, чтобы нарисовать на его месте спрайты (спрайты копируются той же процедурой, что и сам скроллер)
kPPOKE E01_AniSprite1, Phase1Sprite ; настройка корректных фаз для анимации волн вокруг чанкового шара
kPPOKE E01_AniSprite2, Phase2Sprite
kPOKE E01_BlitSprite, 1 ; спрайты начинают рисоваться заранее, скрытые атрибутами
kPPOKE E01_CurTableCT3, PlasmaTabCT3_1 ; выбор таблицы для плазмы внутри шара
kINIT E01_TuneCT3 : db 5, 9, 47 ; настройки эффекта плазмы

kWAIT 2304-1 : kINIT E01_SetScene3 ; собственно переключение сцены. спрайты уже нарисованы...
kWAIT 2304+1 : kPOKE E01_ScrollerBlit, 201 ; ...поэтому скроллер можно выключить
kPOKE E01_BeamsOn, 1 ; и включить вместо него эффект радиальных лучей
kINIT E01_PaletteCT3 ; инициализация палитры для цветных чанков
   db %01110110,%01010110 : db %01110110,%01010010 : db %01000110,%01000010 : db %00000110,%00000010
   db %00000011,%00000001 : db %01000011,%01000001 : db %01011011,%01001001 : db %01011111,%01001011
kPPOKE E01_CurBeamColour, E01_FlashMagenta ; цвет радиальных лучей
kPOKE E01_PickPhase, 1 ; выбор фазы позволяет контролировать направление, в котором будут крутиться лучи
kEXEC E01_JumpCT3, E01_OverlayPage ; заключительная инициализация новой сцены


Поскольку эффект занимается чем-то своим, а ядро — чем-то своим, самой сложной частью для написания такого ядра оказывается менеджер памяти. При работе с таким ядром пользователь не имеет права менять страницы в обход ядра. Ядро ведёт учёт и отличает «страницу ядра», т.е. ту страницу, где хранится скрипт, «страницу музыки» (понятно), «страницу пользователя при работе прерывания» и «страницу пользователя при работе вне прерывания». Есть и другие нюансы, например, моё ядро работает не с реальными, а с виртуальными страницами, благодаря чему на классических машинах я всегда знаю, какие страницы медленные, а какие быстрые. Но я уже устал столько всего писать!
avatar
Сам юзаю ядро любезно предоставленное introspec 'ом. Но чуток переделал под свои нужды. Так как у меня (на примере Kpacku demo) весьма много мультиколора, мне нужно очень точно контролировать положение луча. Поэтому я сделал два обработчика скрипта. Один висит на прерываниях (назвал его внутренним) и используется большей частью для распаковки последующих эффектов. Второй (внешнее) — вызывается из эффекта. Ввёл команды переключения из одного в другое(kIN, kOUT). В kOUT меняется обработчик прерываний на один только RET и передаётся управление эффекту. RET — потому, что максимальное число тактов мне нужно, чтобы всё подготовить за время отрисовки верхнего бордера и при этом знать точное положение луча. И работа с мультиколором подразумевает, что эффект фреймовый, потому время для музыки и быстрого обработчика скрипта найти можно.

lll halt
    ... ; эффект
    call mus_n_counter    ; играем музыку и крутим счётчик фреймов
    call KernelOut        ; вызываем внешнее ядро    
    jp lll

Из внешнего ядра можно вызывать все те же команды, что из внутреннего. Потому, при таком подходе можно так же управлять эффектом из скрипта вызывая kPOKE и т.д.
avatar
ох жестоко вы наворачиваете, парни…
avatar
Кстати, да, мне этого не хватает в моём ядре. Только я не буду разносить их, как ты предлагаешь, а видимо перейду к системе 2х скриптов; один скрипт будет выполнятся во время прерываний, это скрипт специально для коротких, незапирающих команд. А второй скрипт будет выполняться так же, как скрипт у меня сейчас. Это позволит распаковывать и фиксить одновременно, примерно как у тебя сейчас. Но идеология прежняя — ядро должно будет контролировать выполнение.
avatar
Ну вот у меня и было 2 скрипта — один глобальный, который управляет распаковкой и запуском частей, и внутренний в части, который работал на прерываниях в пределах части. Плохо было то, что данные этого скрипта компилились в самой части, а надо было вытащить это как-то в общий скрипт (чтобы для фикса не переписывать ничего кроме одного полутекстового файла). В то время мешала постоянная гонка — не хватало времени между демами.
Ну и надо учесть факт, что в отличие от вас мы почти никогда не упирались на фреймовые эффекты, а всякие мультиколоры вообще были табу )
avatar
Чанки против мультиколора! Fight!
avatar
вапрос.
у меня:
db _call
		dw frstartr

		db _wait
		dw 32*4

		db _poke
		dw fade_mask_bw
		db 1

и выборка данных с парсингом,

у тебя:

kPPOKE E01_CurTableCT3, PlasmaTabCT3_1 ; выбор таблицы для плазмы внутри шара
kINIT E01_TuneCT3 : db 5, 9, 47 ; настройки эффекта плазмы


как сделано?
avatar
Во-первых:
 		MACRO	kPPOKE	addr, value
		db	_kPPOKE
		dw	addr, value
		ENDM
Во-вторых:
_kPPOKE:	EQU	low cmPPOKE
т.е. _kPPOKE — младший байт адреса процедуры, которая собственно и реализует команду.
avatar
отлично. так лучше :)
спасибо
avatar
у тебя получается младший байт для кернеля?
avatar
Да. Если я верно понял вопрос :)
avatar
ок) обещаю здесь показать как у меня всё уже устроено.
надеюсь на конструктивные комментарии)
после 3вм
avatar
или не стОит?
avatar
Насчет наследства — демы ЭКСПЛОДЕРа на таком глубоком уровне не смотрел, учился на его эпичной 4к. Там весь скрипт в виде очередности CALL сделан — так в итоге и работал наш Melange, пара гифтов и даже несколько игр. Идею хранить вызовы/данные на стеке предложил boleg (как помнится). От эксплодера остались всякие низкоуровневые процедурки, ну и сама идея, что невозможное возможно.
avatar
и вот как раз к этому еще какой-нить запилятор да с наборами процедур и вообще можно демку не кодить тока графику и музыку :)
avatar
куча каллов в циклах с метками PIZDA, ZALUPA, e t c )))
avatar
avatar
Вот сразу и чётко видно олдскул. Ядро совершенно не в теме касательно частей. С другой стороны, очень тщательно продуман процесс работы над демой — каждая часть легко выделяется в отдельный исполняемый файл, что очень упрощает отладку и доводку.
avatar
дык тут имеем загрузчик-плеер)
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.