Заранее прошу отнестись к этому вопросу сурьёзно, ибо непонятное поведение движка, наблюдаемое мной, никак не является следствием похмелья или других эффектов от злоупотребления различными нехорошими излишествами
Вопрос этот вызван следующей проблемой: есть класс, и в нём объявлен метод, который ничего не делает, кроме как загружает звук функцией PRECACHE_SOUND. Но когда этот метод выполняется (вызываю я его, естественно, из функции Spawn, т.к. загрузка разрешена только в момент спавна ентитей), то игра вылетает, а в консоль выводится сообщение, что невозможно загрузить МОДЕЛЬ. И в качестве имени модели выдаётся имя того самого звука, который я пытаюсь загрузить.
Ошибок типа "вместо PRECACHE_SOUND я вызвал PRECACHE_MODEL" или "попутал аргумент функции", в коде нету. Я проверил при помощи всяких алертов и т.п, и нет никаких сомнений, что вызывается именно функция PRECACHE_SOUND, и именно она является причиной вот такого сообщения.
И как вообще такое может быть? g_engfuncs я не трогал. Могла ли она как-то неявно перетащиться в другую область памяти так, что адрес pfnPrecacheSound заменился на адрес pfnPrecacheModel?
LLAPb писал: Могла ли она как-то неявно перетащиться в другую область памяти так, что адрес pfnPrecacheSound заменился на адрес pfnPrecacheModel?
Да, могла. В с++ вообще контроля за памятью нет, и из-за неудачного указателя может произойти что угодно.
Но лучше приведи побольше инфы: кусок кода, точный текст, который выводится в консоль, и т.п.
XaeroX Я думаю, кусок кода ничего не скажет, ибо он лишь вызывает PRECACHE_SOUND и всё. А вот насчёт неудачного указателя попрошу поподробней
В коде я в последнее время стал использовать операторы new/delete, мож, это из-за этого? (Но, опять же, они вызываются во многих местах, сюда этот код не влезет) Я чё-то и не предполагал, что в с++ нет контроля за памятью. Динамические массивы могут перемещаться куда хотят - это да, но чтоб и всё остальное...
Добавлено 14-11-2008 в 22:17:
Вообще, с подробной инфой тут проблематично, халфа и сорцы у меня на другом компе...
LLAPb писал: А вот насчёт неудачного указателя попрошу поподробней
ну что тут подробнее?
В с++ вполне законна вот такая конструкция:
C++ Source Code:
char *p = newchar[32];
*(p + 1000) = 100;
Сам понимаешь, что по смещению 1000 может находиться что угодно.
Попробуй сделать вот что. Замени в g_engfuncs указатель на pfnPrecacheSound на твой собственный:
C++ Source Code:
1
typedef (blablabla) PFNPRECACHESOUNDPROC;
2
PFNPRECACHESOUNDPROC engineProc;
3
4
int Wrap_PrecacheSound(char *s)
5
{
6
MessageBox(0,"Precache sound", s, 0);
7
return engineProc(s);
8
}
9
10
...
11
12
engineProc = g_engfuncs.pfnPrecacheSound;
13
g_engfuncs.pfnPrecacheSound = Wrap_PrecacheSound;
Так ты легко определишь, меняется ли структура g_engfuncs.
Еще способ - сосчитать контрольную сумму (хотя бы CRC) этой структуры при передаче в длл и периодически ее проверять.
Ну, что такое CRC я не знаю, но я тупо посчитал количество единичек в адресах. примерно вот так:
C++ Source Code:
1
int size = sizeof(g_engfuncs.BLABLABLA)*8;
2
3
for (int i = 0; i < size; ++i)
4
{
5
checksumm += ((int)g_engfuncs.BLABLABLA)&(1<<i);
6
}
Считал сначала каждый кадр, потом просто до и после вызова самых подозрительных функций - никакого эффекта. Суммы не меняются.
Первый способ, я думаю, не особо надёжен, не стал проверять.
Я конечно понимаю, что в коде у меня куча всего наворочено, и разберусь в этом только я, поэтому просто задам вопрос:
Чем (не конкретно, а например) мог быть вызван такой странный эффект? Я не имею в виду такие ляпы, как выход за границы массива, я имею в виду слегка другое. Ведь, как показала проверка контрольных сумм, указатели на функции не изменились, очевидно, куда-то сместились сами функции
Может это всё-таки связано с new/delete? Не даром же в hl.dll они вообще не юзаются...
LLAPb писал: Может это всё-таки связано с new/delete? Не даром же в hl.dll они вообще не юзаются...
Это маловероятно.
Вообще причина может быть на поверхности.
Сообщение какого вида? Не найдена модель блаблабла.wav или не найдена модель блаблабла.mdl?
Не найдена модель блаблабла.wav. То есть именно тот путь, который я указываю в аргументе функции PRECACHE_SOUND.
Кхм, вообще-то контрольная сумма - вещь тоже не надёжная. Может, функции PRECACHE_SOUND & PRECACHE_MODEL поменялись местами, и сумма их адресов тогда останется неизменной. Я это говорю потому, что закомментировав вызов PRECACHE_SOUND, я наткнулся на ошибку "Не найден ЗВУК sound/models/dom_point.mdl", которая, очевидно, была вызвана функцией PRECACHE_MODEL("models/dom_point.mdl")
Так что я, пожалуй, проверю изменения каждого конкретного адреса в структуре g_engfuncs. Действительно, причина где-то на поверхности, и она, скорее всего, совершенно дурацкая...
А проблемка-то оказалась далеко не на поверхности.
Как мне удалось выяснить всякими там мессагами, алертами и т.п., в движке загрузка моделей/звуков производится то ли отдельным потоком, то ли ещё как-то так, короче, действительно модель начинает загружаться спустя определённое время после вызова PRECACHE_MODEL (даже g_ulFrameCount успевает доползти до 28-36). И при этом функции PRECACHE_blablabla не копируют свой аргумент в отдельный буфер, а используют всё тот же char* , который мы им передали. Так что если по тому адресу, куда указывает этот самый char* , будет другая строка, то мы получим вот такую ошибку.
В моём случае был указатель на буфер, общий для нескольких функций, которые писали туда имена всяких звуков, моделей и т.п.
Поэтому я стал использовать (char*)STRING(ALLOC_STRING(буфер)).
Отсюда следующий вопрос: А как движок обращается с буфером строк, в который пишется аргумент ALLOC_STRING? Если в него добавляются две одинаковые строки, то там и будут две одинаковые строки, или же одна заместит другую? (Потому что мне придётся юзать ALLOC_STRING довольно часто, но аргументы часто могут повторяться. Не вызовет ли это в конце концов переполнение памяти?)
LLAPb писал: А как движок обращается с буфером строк, в который пишется аргумент ALLOC_STRING? Если в него добавляются две одинаковые строки, то там и будут две одинаковые строки, или же одна заместит другую?
Если честно, не знаю Вообще-то поиск строк по большому пулу не такой уж быстрый (хотя они могут юзать хэш-таблицу, и тогда...), поэтому они вполне могли забить на проверки.
Я в движке Volatile так и делал - gi.AllocString у меня вызывал в конечном итоге malloc, и дллки должны сами были своевременно вызывать gi.FreeString (иначе были бы утечки памяти).
XaeroX, да, действительно, в Half-Life наверное так же. Слишком уж редко это ALLOC_STRING вызывается, и нет смысла из-за столь малого количества памяти каждый раз перебирать весь пул или мутить хэш-таблицу...
Хотя, если глянуть с другой стороны, то зачем им было мутить загрузку ресурсов отдельным потоком в игре, в которой при смене карты выскакивает табличка "LOADING" и игровой процесс останавливается?
Мож они для того же понта и хэш-таблицу заюзали...
Дело не в потоке вовсе... Ресурсы грузятся очень хитро. Сначала создается список этих ресурсов. Потом он пересылается с сервера на клиент (даже в случае сингла, на локалхост). Потом клиент получает мессагу, читает ее и грузит все ресурсы. А отправка мессаги идет, видимо, асинхронно, отсюда и ощущение другого потока.
У моделей, звуков и декалей общий буффер примерно на 2000 строк.
Разделение на зоны - только по старт-стоповым номерам.
Т.н. с 16 по 768 - модели, а с 768 по 1200 - звуки (это для примера).
Модельиндекс на сервере - это и есть возвращенный номер строки - пути к вашей моделке.
При старте вся эта волшебная шняжка шлется на клиент, но поскольку строк много, то делает это не за один кадр, а кадров за пять- шесть.
Но пять кадров на сервере, это кадров 30 на клиенте, отсюда и интересные значения g_ulFrameCount (PlayerThink вызывается с клиентским fps, в отличие от сервера).
Стринги, аллокнутые движком не высвобождаются автоматически и неоптимизируются в памяти никак, поскольку неизвестно будем ли мы только читать этот стринг или еще записывать.
Скажу только, что проблема с темп стрингами тянется еще с первокваки, но там её очень проблематично заметить, по причине бедности набора буилтинов. Я обычно выходил из положения создавая static стринги.
Да, Дядя Миша, благодарствую, всё расписал фундоментально и по понятиям
Проблемку со строками этими я решил, создав дурацкий строковый буфер, работающий примерно как STL'овский map. В принципе, для моих целей сойдёт...