> For the complete documentation index, see [llms.txt](https://lessons.sannybuilder.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lessons.sannybuilder.com/00100/00200/000176.-vydelenie-pamyati.md).

# 000176. Выделение памяти

Всем привет! В этом уроке мы ознакомимся с функцией выделения памяти и рассмотрим подробный пример работы этой фишки.

Иногда при проектировании больших скриптов возникают проблемы с хранением каких-либо значений в CLEO-скриптах:

* Количество локальных переменных мало;
* Глобальные переменные не рекомендовано использовать в CLEO;
* Глобальные CLEO-переменные могут привести к конфликтам между скриптами;

Проблемы очевидны и решить их можно несколькими способами. Сегодня мы рассмотрим способ с выделением динамической памяти.

Допустим, у нас стоит задача: создать 100 актёров. Их нужно где-то хранить, и буфер является очень привлекательным вариантом. Рассмотрим опкоды, для работы с ними:

```
0AC8: 0@ = allocate_memory_size 120
0AC9: free_allocated_memory 0@
```

Первый опкод выделяет память под буфер, где `120` — это размер (в байтах), а `0@` — переменная, которая будет хранить указатель на начало области памяти (он же совпадает с началом буфера). Второй опкод освобождает выделенную память.

Возникает вопрос: как узнать размер выделяемой памяти, чтобы нам хватило места для записи этих актёров? Поскольку переменные для создания/записи актёров являются ссылками, то размер одного сегмента этой памяти будут равняться 4 байтам, а общий размер выделяемой памяти — `100 × 4 = 400` байт.

Для того, чтобы получить 1 элемент этого буфера, легче всего сделать несколько SCM-функций, которые делали бы навигацию по этой области памяти. Первый участок кода будет выделять память под актёров, а второй — очистка выделенной памяти:

```
goto @SKIP_FUNCTIONS

:CREATE_ARRAY
0A90: 2@ = 4 * 0@ // 4 * size
0AC8: 1@ = allocate_memory_size 2@
0AB2: ret 1 1@

:DESTROY_ARRAY
0AC9: free_allocated_memory 0@
0AB2: ret 0

:SKIP_FUNCTIONS
```

Я назвал SCM-функции "создать массив" и "удалить массив" согласно принципам, по которым они работают. Теперь добавим возможность читать/записывать в ячейку памяти нужное значение по индексу. Индекс будет определятся как `Номер × размер`, размер ссылки естественно 4 байта:

```
:SET_ARRAY_ITEM
0A90: 3@ = 4 * 1@ // 4 * size
0A8E: 4@ = 0@ + 3@ // Buffer + offset
0A8C: write_memory 4@ size 4 value 2@ virtual_protect 0
0AB2: ret 0

:GET_ARRAY_ITEM
0A90: 3@ = 4 * 1@ // 4 * size
0A8E: 4@ = 0@ + 3@ // Buffer + offset
0A8D: 2@ = read_memory 4@ size 4 virtual_protect 0
0AB2: ret 1 2@
```

Поскольку у нас идёт работа с памятью, мы обязательно применяем опкоды `0A8C` и `0A8D` для чтения и записи её участка. Адрес чтения мы получили за формулой: `Начало буфера + ( размер ссылки × индекс )`. Поэтому наши SCM-функции будут обязательно принимать ссылку на выделенный буфер:

```
0AB1: call_scm_func @CREATE_ARRAY 1 num_items 100 store_to 0@
0AB1: call_scm_func @DESTROY_ARRAY 1 array 0@
0AB1: call_scm_func @SET_ARRAY_ITEM 3 array 0@ index 0 value 0@
0AB1: call_scm_func @GET_ARRAY_ITEM 2 array 0@ index 0 value 0@
```

Итак, запишем в наш массив 100 случайных актёра, разместив их у Гроув Стрит:

```
{$CLEO}
0000:

goto @SKIP_FUNCTIONS

:CREATE_ARRAY
0A90: 2@ = 4 * 0@ // 4 * size
0AC8: 1@ = allocate_memory_size 2@
0AB2: ret 1 1@

:DESTROY_ARRAY
0AC9: free_allocated_memory 0@
0AB2: ret 0

:SET_ARRAY_ITEM
0A90: 3@ = 4 * 1@ // 4 * size
0A8E: 4@ = 0@ + 3@ // Buffer + offset
0A8C: write_memory 4@ size 4 value 2@ virtual_protect 0
0AB2: ret 0

:GET_ARRAY_ITEM
0A90: 3@ = 4 * 1@ // 4 * size
0A8E: 4@ = 0@ + 3@ // Buffer + offset
0A8D: 2@ = read_memory 4@ size 4 virtual_protect 0
0AB2: ret 1 2@

:SKIP_FUNCTIONS

{ !!! НАШ КОД !!! }

0AB1: call_scm_func @CREATE_ARRAY 1 num_items 100 store_to 0@
3@ = 2500.0

for 2@ = 0 to 100 step 1
 0376: 1@ = create_random_actor_at 3@ -1659.0 12.3437
 0AB1: call_scm_func @SET_ARRAY_ITEM 3 array 0@ index 2@ value 1@
 3@ -= 1.0
 wait 0
end

0A93: end_custom_thread
```

Результат:

![100 актёров на Гроу](https://github.com/wmysterio/scm-scripting-lessons/raw/resources/_pu/2/24368649.png)

Как видим, в скрипте использовалась всего 1 переменная `1@` и мы не потеряли полный доступ ко всем остальным актёрам. Давайте теперь удалим созданных актёров и посмотрим работают ли наши функции удаления и очищения памяти:

```
{$CLEO}
0000:

goto @SKIP_FUNCTIONS

:CREATE_ARRAY
0A90: 2@ = 4 * 0@ // 4 * size
0AC8: 1@ = allocate_memory_size 2@
0AB2: ret 1 1@

:DESTROY_ARRAY
0AC9: free_allocated_memory 0@
0AB2: ret 0

:SET_ARRAY_ITEM
0A90: 3@ = 4 * 1@ // 4 * size
0A8E: 4@ = 0@ + 3@ // Buffer + offset
0A8C: write_memory 4@ size 4 value 2@ virtual_protect 0
0AB2: ret 0

:GET_ARRAY_ITEM
0A90: 3@ = 4 * 1@ // 4 * size
0A8E: 4@ = 0@ + 3@ // Buffer + offset
0A8D: 2@ = read_memory 4@ size 4 virtual_protect 0
0AB2: ret 1 2@

:SKIP_FUNCTIONS
0AB1: call_scm_func @CREATE_ARRAY 1 num_items 100 store_to 0@
3@ = 2500.0

for 2@ = 0 to 100 step 1
 0376: 1@ = create_random_actor_at 3@ -1659.0 12.3437
 0AB1: call_scm_func @SET_ARRAY_ITEM 3 array 0@ index 2@ value 1@
 3@ -= 1.0
 wait 0
end

{ !!! НАШ НОВЫЙ КОД !!! }

for 2@ = 0 to 100 step 1
 0AB1: call_scm_func @GET_ARRAY_ITEM 2 array 0@ index 2@ value 1@
 if 
 056D: actor 1@ defined
 then
 01C2: remove_references_to_actor 1@ 
 009B: destroy_actor 1@
 end
 wait 0
end 
 
0AB1: call_scm_func @DESTROY_ARRAY 1 array 0@ 

0A93: end_custom_thread
```

Результат:

![Актёров уже нет Гроу](https://github.com/wmysterio/scm-scripting-lessons/raw/resources/_pu/2/57559530.png)

Улица снова пуста! Интересно понаблюдать за самим процессом создания/удаления этих актёров: видно сколько примерно времени занимает одна итерация цикла.

Думаю, многие заметили, что в цикл я добавил нетипичную для него задержку в 0 милисекунд. В нашем случае без задержки игра вылетит, что конечно не радует. Надеюсь, что этот пример пригодится вашим проектам, где есть смысл хранить данные именно таким способом.

{% hint style="danger" %}
Чтобы не превышать лимиты, не создавайте слишком много сущностей за один раз!
{% endhint %}

{% hint style="danger" %}
Не используйте этот способ для хранения данных, которые нужно восстанавливать после перезагрузки игры!
{% endhint %}

{% hint style="warning" %}
В `main.scm` этот способ нужно использовать с осторожностью, но там ситуация с переменными более благосклонна к нам.
{% endhint %}

Кроме актёров, таким вот образом можно записать и другие сущности, а также целые и/или вещественные числа. Также нет привязки к конкретному типу данных: мы можем в разные ячейки записать разные значения. Главное не забыть что и куда было записано :)

На этом, пожалуй, всё. С Вами был wmysterio!
