# 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!


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lessons.sannybuilder.com/00100/00200/000176.-vydelenie-pamyati.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
