# Сигналы, Компоненты и Элементы
DCS - это сложно. Не только потому, что он использует умные решения для сложных проблем, но и то, что само понимание этих проблем резко меняется. По началу это покажется сложным, но после начального периода обучения вы поймёте суть. Это совершенно новый подход к решению проблем, который упрощает многие варианты решений, распространенные в ss13.
## Терминология
**DCS, Datum component system:** Немного устаревший термин, но это было первоначальное название системы в целом.
**Компоненты:** Изолированные обладатели функциональности. Они содержат все данные и логику, необходимые для выполнения некоторого дискретного поведения.
**Элементы:** То же, что и компоненты, но дешевле и более ограниченны в функциональности. Компоненты часто используются в этом руководстве для обозначения обоих как группы.
**Сигналы:** Способ получения сообщений компонентами. Первоначально только компоненты могли принимать сигналы, но с тех пор он был расширен, поэтому любой может принимать сигнал, если он полезен.
## Компоненты/Элементы
Компоненты - это набор функций черного ящика, разработанный с нуля с учетом сигналов. Подробности того, как работают сигналы, будут подробно описаны ниже, но вы, вероятно, сначала будете взаимодействовать с сигналами, внося изменения в какой-либо компонент или элемент. Функционально элементы представляют собой минимальную версию компонентов, и для целей этого документа вы можете заменить большинство случаев использования слова «компонент» на элемент. Различия будут раскрыты позже, если вы захотите углубиться в детали.
Чтобы назвать несколько существующих примеров компонентов, у нас есть компонент, который обрабатывает объект, издающий шум при ударе/броске/использовании и т. д. Вы можете применить это ко всему, даже к стенам, если хотите. Компонент, который превращает что угодно в хранилище. Компонент, который обрабатывает вращение объектов вручную по персонажу. Компонент, вызывающий распространение болезней. Надеюсь понятно.
## Сигналы
Сигналы - это наша реализация довольно распространенной концепции программирования: событий. Если у вас есть код, который должен срабатывать при выполнении некоторых условий, вы можете написать свой код, чтобы проверять эти условия каждый тик, по шаблону опроса. Напротив, события запускаются самими условиями.
В качестве примера представьте звук шагов. В коде обуви вы можете сделать следующее:
```dm=
/obj/item/shoes
var/turf/last_noise
/obj/item/shoes/process()
. = ..()
var/turf/current_turf = get_turf(src)
if(current_turf == last_noise)
return
last_noise = current_turf
play_footstep()
```
Здесь при каждом такте процесса мы видим, находимся ли мы в новом месте, и если да, то издаем звук шагов. Расточительно мягко говоря.
Вместо этого можно настроить обувь так, чтобы она звучала только тогда, когда человек в ней двигается. Поскольку обувь носят только люди, без сигналов вам нужно будет ввести этот код в человеческий тип. Это также неполноценное решение, поскольку оно означает, что у вас есть логика, которая относится к обуви за пределами кода обуви.
С сигналами все эти проблемы решаются. Человеческому коду не нужно знать или заботиться о том, что что-то, что на нем надето, слушает, когда оно движется, и коду обуви не нужно проверять каждый тик, если он переехал в новое место.
```dm=
/atom/movable/Moved()
SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED)
/obj/item/shoes/equipped(mob/equipper)
RegisterSignal(equipper, COMSIG_MOVABLE_MOVED, .proc/play_footstep)
```
Сигналы: Как они работают
Их концепция довольно проста: сначала вы создаете место, откуда активируется сигнал. Пусть будет сигнал, который активируется, когда кто-то переступает что-то.
```dm=
#define COMSIG_MOVABLE_CROSSED "movable_crossed"
/atom/movable/Crossed(atom/movable/AM, oldloc)
SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM)
```
Здесь у нас есть базовая процедура **Crossed**, настроенная так, чтобы отправлять сигнал и больше ничего не делать. src дается в качестве аргумента, так что это сигнал с самим собой в качестве источника. Далее указывается тип сигнала, он определяется в **_\_DEFINES/components.dm**, а затем передается в сигнал. Функционально это просто строка, выступающая в качестве идентификатора. Третий аргумент в сигнале - это перемещаемое движение, которое передается тому, кто получает сигнал.
Теперь, когда у нас есть сигнал в верхней части цепочки наследования, любые другие переопределения процедуры **Crossed** должны будут либо вызывать родительский элемент с помощью ..(), либо отправлять тот же сигнал, что и мы, чтобы убедиться, что этот сигнал вызван всякий раз, когда вызывается процедура. **По возможности избегайте дублирования сигналов**. Сигналы должны иметь единый источник происхождения, чтобы было как можно проще рассуждать о поведении при получении этих сигналов. Если вы чувствуете необходимость в дополнительных источниках сигналов, вам следует рассмотреть возможность создания дополнительных типов сигналов. Вы можете свободно подавать больше типов сигналов.
Теперь, когда существует этот новый сигнал, мы можем сделать что-то способным его прослушивать.
```dm=
/datum/component/squeak/Initialize()
RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, .proc/play_squeak_crossed)
/datum/component/squeak/proc/play_squeak_crossed(datum/source, atom/movable/AM)
[dostuff]
```
Здесь, когда объект, в данном случае компонент, создается, он регистрируется для сигнала. Теперь он «слушает» этот сигнал, и каждый раз, когда сигнал будет отправлен, компонент будет знать об этом. Любые аргументы, данные в этом сигнале, передаются слушателю. Затем это вызывает процедуру, указанную при регистрации сигнала. Вы можете вызывать любую процедуру таким образом, посмотрите как работают **callbacks**, чтобы увидеть, как это работает.
Откуда взялись эти данные/источник? Сигналы в дополнение к своим обычным аргументам будут каждый раз передавать отправителя сигнала в качестве первого аргумента. Это полезно, когда что-то слушает один и тот же сигнал на нескольких других объектах.
Теперь у вас есть рабочий компонент, получающий сигнал от объекта, к которому он применяется, но что это за компонент?
## Изоляция плохого кода с помощью компонентов
Компоненты - это исходная причина, по которой были добавлены сигналы. Сигналы, возможно, перешли к более серьёзным вещам, но компоненты по-прежнему являются очень мощным инструментом для реализации сложных функций, которые легко поддерживать. Компоненты отвечают только за себя, они, как правило, просто реагируют на простые события. Им все равно, чем еще занимается их владелец или как он это делает. Если компонент писка получает сигнал о том, что кто-то наступил на его владельца, то, черт возьми, он издаст звук.
Как упоминалось ранее, основной способ взаимодействия компонентов с миром - это сигналы. Но есть еще пара способов, к которым мы сейчас перейдём.
Компонент имеет ссылку на владельца этого компонента. Это чаще всего используется для получения некоторой информации о состоянии, такой как локации владельца, чтобы знать, где воспроизводить звук и т. д.
Другой способ взаимодействия с компонентами - процедура **GetComponent()**. Эта процедура - простой и понятный - костыль. Что он делает, так это позволяет вам получить определенный тип компонента, который был применен к объекту. Такой метод взаимодействия с компонентами вредит их полезности. Если у вас есть компонент, функциональность которого зависит от внешнего кода, вы снова возвращаетесь к спагетти-коду. Вы не можете узнать, видите ли вы все возможные пути, по которым может идти логика, просто взглянув на компонент. Сначала поищите другие методы и попросите о помощи, если вы все еще чувствуете, что вам действительно нужно использовать **GetComponent()**.
Это нужно повторить: **Процедура GetComponent является костылем и вредит вашему компоненту. По возможности избегайте её.**
Взгляните на следующий код. Это компонент, который плохо реализует способ изменения стоимости проданного товара:
```dm=
/type/path/item
var/value = 0
/type/path/item/proc/appraise()
var/datum/component/value/comp = GetComponent(/datum/component/value)
if(comp)
value = comp.value
return value
```
Обратите внимание, что компонент на самом деле ничего не делает. Он ничего не разделяет. Мы просто используем компонент для хранения значения, чтобы сказать, что мы используем компонент.
Есть много способов сделать это правильно, и все они включают сигналы:
```dm=
/type/path/item
var/value = 0
/type/path/item/proc/appraise()
SEND_SIGNAL(src, COMSIG_ITEM_APPRAISING)
return value
/datum/component/value/Initialize()
RegisterSignal(parent, COMSIG_ITEM_APPRAISING, .proc/appraising)
/datum/component/value/proc/appraising()
source.value = value
```
Мы удалили **GetComponent**, этот объект больше не зависит от существующего компонента. Все в предмете является общим, и компонент обрабатывает исключительные случаи. Другие компоненты также могут использовать этот же сигнал, если их эффекты связаны со стоимостью предмета.
Сохраняйте код для конкретной функции как можно более изолированным от соответствующих компонентов, и вы получите более удобную в обслуживании, более расширяемую и более устойчивую к ошибкам систему. Затраты на производительность минимальны для всего этого и во многих случаях дешевле на процессор. С точки зрения памяти, в крайних примерах потенциально есть проблемы, но элементы предназначены именно для этого.
## Оптимизация при помощи элементов
Суть в том, что элементы - это легкие компоненты. Существует только один экземпляр для каждого типа элемента, и этот единственный экземпляр присоединяется к каждому объекту, который использует этот элемент. Это сделано для того, чтобы позволить разделить поведение, которое не является особенно быстрым в компонентах из-за ограничений памяти или общего пиздеца.
## Разработка новых компонентов/элементов
Если вы хотите создать новый компонент, остановитесь и подумайте, чего именно вы пытаетесь достичь. Компоненты, как правило, должны создаваться как держатели для поведения, изолированного от того, как это поведение используется. Компонент волшебника, включая требования к мантии и тому подобное, вероятно, не самый лучший выбор, как общий компонент заклинателя, который дает возможность читать заклинания.
После принятия решения о концепции вашего компонента вы можете вместо этого рассмотреть элемент, если этот компонент будет интенсивно использоваться или будет очень простым. Компоненты обладают большей гибкостью, чем элементы, но элементы очень дешевы в использовании. Не волнуйтесь, если вы не можете решить, и просто создайте компонент, если не видите, как этот элемент будет работать.
Возможно, вы нашли компонент или элемент, который делает почти то, что вы хотите, поэтому вы хотите поработать над этим, отлично! Если это компонент, не делайте подтип. Компоненты имеют все необходимое, чтобы иметь любое поведение для всех вариантов использования одного типа. Если вам нужен другой компонент, вам, вероятно, следует разделить этот компонент на несколько разных компонентов. С другой стороны, если это элемент, это не совсем так. Вы не должны создавать подтипы элементов для добавления нового поведения, которое может легко уйти в один элемент, если ваша область действия достаточно узкая. Если вам нужна небольшая конфигурация в вашем элементе, вам нужен индивидуальный элемент. (Можно расширить руководство, чтобы поговорить об этом позже, за этим делом идите в оригинальную статью)
Если вы впервые создаете новый компонент, то сначала убедитесь, что он вам точно нужен. Трудно сформировать правильную логику для ваших первых нескольких компонентов, более опытные люди могут помочь вам с проблемами до того, как они возникнут.
**Оригинал:** https://hackmd.io/@tgstation/SignalsComponentsElements