Открытая версия виртуальной машины JASS, написаная на FreePascal и содержащая некоторые улучшения.
В приложеном архиве есть две версии библиотеки: нормальная и Limitless, в которой отключен лимит операций.
Список изменений
Остальные изменения в комментариях.
- 2023.04.02
- Добавил совместимость с модом RedirectCalls.
- 2023.03.29
- Оптимизировал работу со стэком, теперь время сортировки упало с 60-ти секунд до 54-ех.
- 2023.03.28
- Теперь нативки получают копии строк из аргументов, так что, в случае перевыделения таблицы строк, указатели не сломаются.
- Теперь алгоритм склеивания строк аналогичен таковому у игры.
- Исправлены ошибки связанные с нулевыми строками.
- Теперь система подсчета ссылок учитывает значения в регистрах и стеке. Благодаря этому строки больше не утекают, но производительность просела: время сортировки поднялось с 40-ка секунд до 60-ти.
- Исправлена моя ошибка, из-за которой локальные переменные не удалялись.
- Теперь между вызовами функций инициализации глобалок проводится очистка потока, чтобы предыдущие вызовы не мешали следующим.
Отличия
По возможности, я старался сделать лучше, чем в оригинале, так что есть различия.
Также есть недоработки.
Также есть недоработки.
Переменные
- Каждый раз, при доступе к переменной или функции, игра хэширует (игнорируя уже имеющиеся в кэше значения) их имена и ищет их в хэштаблице, на что тратится лишнее время. Здесь же объекты достаются из массива по индексу, на что требуется константное время.
- При доступе к переменным, игра в первую очередь предпочитает локальные, но если такой не обнаруживается, то использует глобальную. Проблема в том, что поиск глобальной переменной происходит независимо от результата поиска локальной и на это всегда напрасно уходит двойное время. Здесь это исправлено.
- По выходу из функции, стекфрейм со всеми переменными уничтожается, я же провожу их минимальную очистку, чтобы затем эффективно переиспользовать.
Массивы
- При объявлении переменной-массива, изначально та не занимает памяти. Объект создается при первой записи и память для элементов выделяется по необходимости. Так как максимальный размер массива всего 8192 элемента, которые в суме занимают лишь 32 КиБ, я решил сразу создавать массивы с полным объемом, чтобы не тратить постоянно время перевыделение памяти.
Строки
- Каждый раз, когда в переменную помещается литеральная строка (значение которой прописано прямо в исходном коде, например: "Hello, World!"), игра расчитывает её хеш и производит поиск в хэш-таблице. У меня же это просто операция присвоения переменной числа с индексом строки, без лишних движений.
- Счетчик ссылок строк сломан, из-за чего тот постоянно растет, что может привести к переполнению и строка будет преждевременно уничтожена. У меня исправлено.
- Любая созданая строка будет существовать до конца игры (память утекает), я же уничтожаю строки, на которые не остается ссылок.
- Игры не обнуляет автоматически локальные переменные по выходу из функции, что приводит к утечке ссылок. Я же обнуляю хотя бы строки, чтобы те могли уничтожиться, после того как станут ненужны.
Событие изменения переменной
- У игры есть возможность подписаться на изменения переменной по имени. Из-за этого, при каждом исполнении инструкции записи в переменную, интерпретатор вызывает функцию обратного вызова, которая производит медленный поиск подписчиков в хэш-таблице, на что тратится лишнее время даже при условии отсутсвия таковых. У меня для этого достаточно лишь проверить ячейку массива по индексу. При желании можно скомпилироваться без этой функции, но тогда карты, использующие эту сомнительную возможность, перестанут работать (например, "Life in Arena").
Замеры производительности
В целом, следовало бы сделать полноценный тест с участием различных алгоритмов, но пока что есть только это.
Для оценки скорости сравнивалось время, затраченное на сортировку методом пузырька массива, длинною 8192 элемента, содержимое которого было предварительно отсортировано наоборот.
Для оценки скорости сравнивалось время, затраченное на сортировку методом пузырька массива, длинною 8192 элемента, содержимое которого было предварительно отсортировано наоборот.
Производительность ниже, чем могла бы быть, из-за того, что компилятор отказывается inline'ить некоторые функции.
Длинна имен переменных | Время с модом | Время без мода |
---|---|---|
1 | 77.75 | 98.05 |
4 | 76.25 | 111.95 |
12 | 76.88 | 168.23 |
Время указано в секундах.
Тестовая карта
Можете скачать карту и повторить тест на своих компьютерах.
Для её запуска требуется мод (исходники), добавляющий новые нативки для получения текущего времени и для отключения лимита операций, так как иначе игра прервет поток из-за большого количества выполненых действий.
Смените его расширение на ".mix" и киньте в папку с игрой или воспользуйтесь инжектором.
В свою очередь, мод нуждается в библиотеке для регистрации нативок, она тоже должна присутствовать в папке игры.
Для её запуска требуется мод (исходники), добавляющий новые нативки для получения текущего времени и для отключения лимита операций, так как иначе игра прервет поток из-за большого количества выполненых действий.
Смените его расширение на ".mix" и киньте в папку с игрой или воспользуйтесь инжектором.
В свою очередь, мод нуждается в библиотеке для регистрации нативок, она тоже должна присутствовать в папке игры.
Мод с нативками не способен повлиять на новый интерпретатор, так что для тестов воспользуйтесь Limitless версией, в которой изначально отключены проверки на количество операций.
Список добавляемых нативок
native GetTickCount takes nothing returns integer
native SetOperationLimitEnabled takes boolean state returns nothing
native IsOperationLimitEnabled takes nothing returns boolean
Когда карта загрузится, наберите в чат "-start" и начнется сортировка.
Игра зависнет и то, на сколько это продлится, будет зависеть от силы ядра вашего процессора.
Игра зависнет и то, на сколько это продлится, будет зависеть от силы ядра вашего процессора.
Ред. IceFog
Ред. ScorpioT1000
Моддеры часто пользуются этой возможностью.
Ред. IceFog
Еще есть Rust, но писать хаки на этом языке, сосредоточеном на безопасности, будет тем еще развлечением.
Лишь недавно я начал изучать C++, благодаря которому появился выбор и на котором теперь я буду предпочитать делать все новые моды.
Ред. ScorpioT1000
Ред. IceFog
WriteLn
printf
Исправляется ручным конвертированием значения в тип, который точно вместит в себя все его значения:
Память мертвого потока