Добавлен , опубликован
Раздел:
Разное
Когда у игры неполучается выделить память, она показывает сообщение об ошибке "Недостаточно памяти для обработки команды".
Произойти это может, если память доступная процессу игры кончилась.
Даже если у вас 64-битный процессор и десятки гигабайт оперативной памяти, игра 32-битная и не может использовать больше 4-ех гигабайт памяти.
А на практике, даже больше 2-ух гигабайт будет проблемно занять, из-за особености устройства используемых варкрафтом связных списков.

Регионы памяти

В конце отчета об ошибке, создаваемого игрой при краше от нехватки памяти, можно увидеть таблицу регионов памяти.
Пример таблицы
   12  2784 e:\Drive1\temp\buildwar3x\War3\Source\UI/CCommandButton.cpp(546)
    1   528 .\FrameDef.cpp(69)
   12   768 e:\drive1\temp\buildwar3x\war3\source\game\CFogMaskTable.h(98)
    1    56 .\CGameUI.cpp(517)
    1   512 e:\Drive1\temp\buildwar3x\War3\Source\WorldEdit/WEUtilities.cpp(3422)
  286 12308 .\W32\OsISndCache.cpp(991)
  143  4004 .?AUNATIVETOKEN@@(-2)
...
Первая колонка содержит количество выделенных блоков памяти, а вторая их сумарный размер.
Дальше идет либо имя исходного файла и номер строки, либо закодированое имя типа и отрицательное число.
По моим наблюдениям, выделение памяти может также провалиться в случае, когда размер одного региона менеджера памяти достигает лимита, равного примерно 256-ми мегабайтам.
Так что, даже если память еще не закончилась, но блоков памяти в одном регионе выделено слишком много, то игра всё равно может крашнуться. Также, могу предположить, что если игра попытается одним куском выделить много памяти (например, 300 МБ), то последствия будут теми же.
По сигнатуре региона можно попытаться догадаться о причине произошедшего.
Например, если имя объекта CUnitListNode, то можно заподозрить утечки памяти, связанные с неудаляемыми групами в скрипте карты.

Читаемая таблица регионов

Для более удобного просмотра таблицы, предлагаю воспользоваться моим скриптом на питоне, который сортирует элементы таблицы и конвертирует их в более читаемый вид.
Установите python последнией версии (3.x), но может быть и на старых заработает.
wc3memsort.py
import sys
import re
from contextlib import nullcontext
from collections import namedtuple

def open_input(path):
	return open(path, 'rt') if path != '-' else nullcontext(sys.stdin)

def open_output(path):
	return open(path, 'wt') if path != '-' else nullcontext(sys.stdout)

input_path = sys.argv[1]
output_path = sys.argv[2]

with open_input(input_path) as f:
	input_data = f.readlines()

pattern = re.compile(r' *([0-9]+) +([0-9]+) +(.*)\((-?[0-9]+)\)')

MemoryRegion = namedtuple('MemoryRegion', ['blocks_count', 'size', 'source_file', 'source_line'])

def parse_line(line):
	m = pattern.match(line)
	if m:
		return MemoryRegion(
			blocks_count = int(m.group(1)),
			size = int(m.group(2)),
			source_file = m.group(3),
			source_line = int(m.group(4)),
		)

def parse_lines(lines):
	for i, line in enumerate(lines):
		parsed_line = parse_line(line)
		if parsed_line:
			yield parsed_line
		else:
			raise Exception(f'Failed to convert line number {i+1}.')
			
memory_regions = list(parse_lines(input_data))
total_size = sum(r.size for r in memory_regions)
sorted_memory_regions = sorted(memory_regions, reverse=True, key=lambda r: r.size)
formatted_memory_regions = (format_memory_region(r) for r in sorted_memory_regions)

def format_size(size):
	suffixes = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB']

	i = 0
	while size >= 1024:
		size /= 1024
		i += 1

	if i >= len(suffixes):
		i = len(suffixes) - 1

	if i > 0:
		return f'{size:.2f} {suffixes[i]}'
	else:
		return f'{size} {suffixes[i]}'

def format_memory_region(r):
	pretty_size = format_size(r.size)
	return f'{pretty_size}\t{r.blocks_count}\t{r.source_file}\t{r.source_line}'

with open_output(output_path) as f:
	f.write(f'total size: {format_size(total_size)}.\n')
	f.write('\n'.join(formatted_memory_regions))
	f.write('\n')
Применение:
python wc3memsort.py <путь к файлу с исходной таблицей> <путь к файлу для записи результата>
Вместо пути может быть указан минус "-", в таком случае для чтения или записи будет использоваться стандартный ввод/вывод.
На вход скрипт принимает не весь отчет об ошибке, а лишь фрагмент с таблицей находящийся в конце файла между разделяющими полосами.
На выходе пишется сначала сумарное количество памяти, занимаемое всеми регионами памяти, а затем таблица регионов, в которой первая колонка содержит размер региона памяти, а вторая количество блоков памяти. Дальше, как и в оригинале, идет сигнатура региона.
Пример результата:
total size: 670.44 MiB.
145.41 MiB	1584	.?AVCAgentBaseAbs@@	-2
62.51 MiB	95047	.\cmemblock.cpp	372
59.05 MiB	40199	.?AVC3Vector@NTempest@@	-2
51.49 MiB	1468	.?AUCustomObjectField@@	-2
51.28 MiB	729588	.\CDataAllocator.cpp	152
...
`
ОЖИДАНИЕ РЕКЛАМЫ...