Всем привет. Решил я немного наполнить журнал всякой писаниной по поводу программирования и всего такого. Но здесь меня поджидала сложность: с одной стороны текст хочется сделать интересным, а код прозрачным; с другой - у меня уже довольно много самописных модулей, от которых не хочется отказываться при написании демок или при разъяснении работы тех или иных хитростей программирования. Потому, чтобы всем было просто и уютно, я решил сначала начать с маленьких модулей, расписать их назначение и работу, а затем уже с чистой совестью подключать при необходимости в демонстрационных программах не боясь вопросов "а что это за модуль ты подключил?" Итак, начну с самого простого - ведения лога в играх. Что это? |
По-простому: файл в который программа записывает информацию о своей работе. Зачем это нужно? Не знаю, бывал ли у вас случай (если нет - вам в какой-то степени повезло) слышать фразу: "твоя программа у меня не запустилась, вывелось сообщение что-то вроде Access violation" - очень и очень неприятно. В такие моменты чувствуешь свое бессилие - ведь по такой информации определить почему программа "упала" практически невозможно. Начинаешь задавать вопросы что-то вроде "а в какой момент? что при этом нажимал?" и т.д. Как вы все понимаете - юзер меньше всего знает, почему программа не работает и что вообще она делала в этот момент. Грамотным в такой момент будет ответ вроде: "пришли мне лог-файл, лежащий в папке трам-пам-пам".
Когда-то давно и я сделал для себя модуль, умеющий сохранять всякую информацию на диск.
Его можно найти по ссылке.
В этом модуле вы найдете один-единственный класс TLogManager:
TLogManager = class protected fFileName: String; fLogStrings: TStringList; fLastLogString: Integer; fLastSaveTime: Single; fReSaveTime: Single; Procedure SaveLogToStream(aFileStream: TStream); Procedure ReCreateFile; public Procedure SaveLogToFile; Procedure AddStringToLog(aString: String); Procedure DropStringToLog(aString: String); Procedure AddTimeToLog; Procedure DoProgress(const DeltaTime: Single); Constructor Create(const aFileName: String; const aReCreateFile: Boolean); Destructor Destroy; override; end;
и три функции:
Procedure Drop_StringToLog(aString: String); Procedure Add_StringToLog(aString: String); Procedure Set_LogManager(aLogManager: TLogManager);
Как это все работает? Очень просто!
Первым дело необходимо создать основной объект, который будет заниматься логом:
fLog := TLogManager.Create('log.txt', True);
Думаю, здесь все ясно. Мы создаем объект класса TLogManager, указываем файл log.txt в качестве файла для записи и выставляем флаг пересоздания файла (а не дописывания в конец) в значение "правда".
Теперь осталось установить этот объект в качестве основного объекта логов:
Set_LogManager(fLog)
Все! Настройка закончена.
Также важно позаботиться о регулярном вызове метода fLog.DoProgress(DeltaTime). Для чего? Штука в том, что доступ к файлам на жестком диске не всегда быстрое дело. Поэтому в данном случае используется следующая схема: сначала весь текст лога хранится в оперативной памяти, и только изредка (сейчас в коде жестко прописаны 30 секунд) вся накопившаяся информация записывается в файл лога.
Теперь из любого места программы можно вызвать:
- Drop_StringToLog(), если мы хотим тут же записать данные в лог-файл (выполняется перед критическими секциями, в которых возможен "вылет программы")
- Add_StringToLog(), если мы хотим отложить запись данных в лог.
Вот и всё. Просто, быстро и удобно. Не забудьте также уничтожить объект перед закрытием программы. Этот модуль я писал довольно давно и до сих пор им пользуюсь. Он более-менее безопасный: при отсутствие объекта для логирования информации, функции по добавлению информации просто не будут ничего выполнять.
Что же можно записывать в лог-файл? Да что угодно! ФПС вашей игры; уровни, в которые играл юзер в эту сессию; время прохождения каждого уровня; конфигурацию компьютера; отсутствие текстур/звуков/моделей игры; лицензионный ключ, используемый для игры и т.д!
Что же можно записывать в лог-файл? Да что угодно! ФПС вашей игры; уровни, в которые играл юзер в эту сессию; время прохождения каждого уровня; конфигурацию компьютера; отсутствие текстур/звуков/моделей игры; лицензионный ключ, используемый для игры и т.д!
Были у меня идеи и по созданию объекта в инициализационной секции, также витала идея насчет обработки сохранения информации по тикам внутреннего таймера - но все это по тем или иным причинам было откинуто. Возможно, когда-то наступит необходимость усовершенствования этого класса, но пока, вроде бы, все уютно. А вам как кажется?
Вообще, сегодня я планировал описать работу со звуковым движком Squall, но не сумел, так как увидел, что мой модуль для этой системы пронизан вызовами из uLog. Поэтому решил для начала осветить сторону, связанную с логированием ошибок и молчаливым продолжением работы программы.
Рад любым идеям, предложениям и пожеланиям.
А ведь в GLScene есть свой компонент-логер ;)
ОтветитьУдалитьYarov, угу, но он вроде новый совсем - я еще не пробовал... мой вариант наипростейшего лога был написан очень-преочень давно.
ОтветитьУдалитьа вообще - спасибо за наводку, гляну как будет время!
В чем причина отказа от секций initialization/finalization?
ОтветитьУдалитьУмеет ли логгер писать критикал ерроры, сбрасывает ли он отчет принудительно при таких ошибках и/или аварийных завершениях программы?
Это риторические вопросы, сам пытаюсь выкроить время, что бы с этими проблемами разобраться. Вкупе с многофункциональной ните-независимой консолью для дебага, чтобы не терять контроль над приложением, даже если оно зависло :)
perfect daemon, лично мне очень не нравится скрывать создание/убийство объектов в initialization/finalization. я просто не люблю с ними работать, оттого бывает вводят в ступор какие-то баги при закрытии программы, связанные с тем, что я торможу, и не проверяю finalization-часть. наверно, такую мою нелюбовь можно сравнить с детской травмой))... вроде ничего серьезного, а вроде и не использую никогда.
ОтветитьУдалитьпо делу: сам логгер очень простой. пишет только ту информацию, что сам подкидываешь. сбрасывает в файл либо по приказу, либо по прохождению 30 секунд с последнего сброса. лично мне сейчас логгер помогает при загрузке текстур, звуков (точнее их отсутствии), а также при ошибках в скриптах.
насчет консоли в отдельной нити - это круто, я пока в этом не силен, да и задачи у меня скромнее: безумные краши с неясными причинами не случались уже давно.
p.s. хотя есть один баг в виде краша - никак отловить не могу, так как проявляется на чужом компе и только при одном извращенном условии, которое я в программе отрубил в итоге. из-за этого на всех остальных компах утекает где-то 1kb памяти за всю игру, зато полностью избавился от краша на "избранном" компе))..
Я начал думать об консоли в отдельной нити, а также о навороченном логгере, когда начал писать движок, архитектура которого предполагает передачу классов, записей и указателей из dll-ки рендера через dll-ку ядра в dll-ку, в которой пишется основной код игры. Передача классов без COM и интерфейсов, все на абстрактных классах. Именно там, в условиях трех адресных пространств (не считая запустившего все это ехе) я столкнулся с тем, что отлаживать неуловимые баги трудно, в особенности баги с неверной передачей ссылок на поля типа объект при приведении класса к абстрактному. (Одно радует, для конечного пользователя нет нужды знать это)
ОтветитьУдалитьГоворя проще, когда архитектура, такая хорошая, простая и гибкая снаружи, стала приносить кучу проблем внутри при реализации я понял, что ни черта не знаю о дебаге неуловимых или трудно угадываемых багов :)
о-о-о... все держать в отдельных dll-ках - это круто! не всегда понимаю, зачем такое может понадобиться в играх (все-таки скрипты сейчас очень быстрые), но сам факт существования такой системы заслуживает уважения!
ОтветитьУдалитьмне с таким пока не доводилось встречаться, так что задачи писать такую хитрую систему дебага не возникало.
это все, как я понимаю, используется в вашем движке? вроде анонсировано, что вы пока закрыли доступ к проекту из вне... когда планируете в массы, что называется, свое творение показывать? очень любопытно глянуть, если честно!