Экспрэшн чипы
Документация 1.05
Введение:
Экспрэшн чипы не вводят никаких новых возможностей, кроме тех что уже существуют. Однако они позволят вам создавать более сложные и компактные установки используя минимальное кол-во чипов, одновременно позволяя вам повторно использовать ранее созданные экспрэшн чипы. Экспрэшн чип комбинирует в себе возможности всех ранее доступных чипов в один. Также используя некоторые дополнительные функции вы сможете заменять экспрэшн чипами элементы памяти.
CPU чип:
Чтобы убрать любые недопонимания относительно различия между экспрэшн чипами и CPU, сначала важно понять, что вообще из себя представляют чипы (в данной ситуации говорится о чипах в целом.) чип – это компонент который считывает данные со входов и основываясь на этих данных выводит обработанную информацию. В теории если есть входящяя информация то должна быть и исходящяя. На практике же чипу не обязательно иметь входящую информацию для получения исходящей, например чипы Pulser и чипы памяти. Как вы возможно заметили экспрэшн чипы на самом деле не похожи на остальные чипы, потому что они имеют внутренние переменные, но во всех остальных отношениях они одинаковы. Они не могут действовать самостоятельно, в отличии от CPU, он может обрабатывать и выдавать информацию без (в теории) какой либо входящей информации. Поэтому CPU всегда обновляет исходящую информацию, а экспрэшн чип всегда ждет входящую информацию для выдачи исходящей.
Главная идея которую следует понять это то что экспрэшн чип это калькулятор, а CPU чип это компьютер. CPU может делать все тоже самое что и экспрэшн чип. Главное отличие между ними в том что CPU может использоваться для очень сложных установок, использующих алгоритмы и функции требующие память, циклы, перенаправление и так далее. Экспрэшн чипы линейны и каждая строка кода обрабатывается хотябы раз за каждое исполнение кода, также эксп.чип имеет очень ограниченные способности памяти.
Чипы:
Сами по себе эксп. чипы работают также как и все остальные чипы однако для того чтобы вы могли заспавнить его вам потребуется ввести правильную программу. Чтобы это сделать необходимо нажать на кнопку «New Expression». После того как вы заспавнили чип вы можете загружать в него новую программу всеголишь щелкнув левой кнопкой мыши по нему. Также вы можете извлеч программу из него щелкнув правой кнопкой мыши, после этого программа будет показана на интерфейсе чипа (в меню). Используя кнопку перезарядки вы можете обнулить все переменные.
Если вы будете обновлять программу в чипе и не будете менять имена входв/выходов то все соединения с другими компонентами будут сохраняться, но если будет удален или изменен 1 из входов/выходов то соединение с ним будет удалено (под соединением понимается Wire)
Если у вас в программе есть ошибка то вы будите предупреждены о ней сообщением о типе ошибке. Сообщения об ошибке не очень полные или умные. Они лишь указывают на то что ожидалось найти или не ожидалось. Например если вы получили сообщение "Expected (() near (,)" то это значит что ожидалось найти «(» перед “,” в строке "A = fun, B = 0" есть такая ошибка, потому что в данном случае “fun” написано с маленькой буквы, а значит это вызов функции и требует скобок после себя "A = fun(), B = 0".
Переменные:
Переменные способны хранить и передавать информацию, как память в калькуляторах (M1 M2 и т.д.)
Внимание! Все имена переменных должны начинаться с большой буквы латинского алфавита чтобы быть правельно интерпретированными. Также все те переменные которые не связаны с входами будут сохранять свое значение между вычислениями. Тоесть если вы зададите значение не входящей переменной равное 1 то оно таким и останется пока вы его не измените. Эти знания принципов работы внутренней памяти позволят вам создавать сложные установки..
Премеры переменных:
A
Out
TotalDistance
Входы и Выходы:
Входы и Выходы абсолютно такие же переменные как и остальные, единственная важная вещь это то что свои значения они получают из входов и выдают на выходы(одноименные) эксп. Чипа.
Тоесть неважно что присоединено ко входу «А», но каждый раз как значение на входе «А» меняется то запускаются вычисления в чипе. Также значение на входе «А» связано с переменной «А» такчто оно может быть использовано во время вычислений, похожая ситуация и с выходами, неважно что подключено ко выходу «В», оно получит значение переменной «B» полученное во время последнего вычисления.
И входы и выходы определяются как последовательность переменных, разграниченных единственным пробелом.
Пример:
A B C
Если вы когда либо смотрели файлы сохраненных эксп.чипов или видели их на форумах то вы наверняка видели 3 линии в начале начинающихся с «@». Это определение ярлыков
Имя (N@), входы (I@), выходы (O@).
Также вы можете описывать переменные не связанные не со входами не с выходами. Такие переменные называются переменными сессии и они сохраняются между каждым вычисленем. Тоесть если вы определите значение для переменной «С» то оно будет сохранено до следующего вычисления или пока оно не будет изменено. Это позволяет делать еще более комплексные вещи, но это опасно изза того что приходится постоянно обнулять переменные в начале каждого вычисления.
Пример:
#Пример плохого кода: как только переменная «Speed» достигнет отметки 50 переменная «Break» примет значение 1 и не будет его менять никогда.
Speed >= 50 -> Break = 1;
#Пример хорошего кода: Переменная «Break» обнулится как тоько переменная «Speed»
будет меньше 50
Break = 0, Speed >= 50 -> Break = 1;
#Еще лучше и легче для понимания
Break = (Speed >= 50 ? 1 : 0)
Функции:
Определенные функции поддерживаются для того чтобы дать вам доступ к дополнительным возможностям при создании различных типов установок. имена функции всегда начинаются с маленькой буквы и продолжаются скобками.
Внимание! Если функция не определена (неправильно написано имя) то она примет значение -1
Примеры некоторых функций:
#Абсолютное значение -2 = 2
abs(-2)
#Округление 3.42 = 3
floor(3.42)
#Конвертация 67 из градусной меры в радианную 67=1.169...
rad(67)
**Описание всех поддерживаемых функций ищите в конце документации.**
Арифметические операции:
Тут все просто.
N + N : сложение
N - N : вычитание
N * N : умножение
N / N : деление
N % N : остаток от деления "4 / 5 = 2.5 => 0.5", замечание: "-1 % 3 = 2"
N ^ N : возведение в степень
-N : отрицание, меняет знак числа
все арифметические действия выполняются слева на право тоесть «2 / 2 / 2» означает
"(2 / 2) / 2" а не "2 / (2 / 2)"
Примеры:
#складывает 2 и 2 = 4
2 + 2
#перемножает 4 на 2 = 8
4 * 2
#делит -4 на -2 = 2
-4 / -2
#складывает 4 и 2 а потом возводит результат в квадрат = 36
(4 + 2) ^ 2
Операции назначения(присвоения):
Операторы назначения используются для присовения значений переменным, в теории существует только один такой оператор, а именно «=».
V = N : назначение
V += N : назначение, используя сложение
V -= N : назначения, используя вычитания
V *= N : назначения, используя умножение
V /= N : назначения, используя деление
V %= N : назначения, используя остаток от деления
V ^= N : назначения, используя возведение в степень
Пример:
V+=N - здесь при каждом вычислении мы прибавляем к V, N и сохраняем значение переменной V в обычном програмировании это выглядело бы как V = V + N
Также и с остальными операторами присваивания. (Если говорить по русски то с каждым новым вычислением переменная V увеличивается на N.)
Операции сравнения:
Операторы сравнения используются для вывода нулей и единиц. «меньше ли Х чем 42», «равен ли Y, 20», они так же используются для создания условий, которые направлены на создание набора требований для включения чего либо, которые так же могут быть совместно использованы с логическими операторами, объясняемыми в следущем разделе.
N == N : равно
N != N : неравно
N > N : больше чем
N < N : меньше чем
N >= N : больше либо равно
N <= N : меньше либо равно
Внимание! Операторы сравнения в экспрешн чипе отличны от таких же в других чипах, а именно ноль это ложь (0) и не ноль это правда (1), что отличается от обычных чипов, где отрицательное значение и ноль это ложь (0), а не ноль это правда. Причиной этого явился выбор програмной семантики, хотя возможно это и изменится в будущем. И ещё, чип, как и все остальные, определяет равенство используя дельту 0.001, а это значит что «0 = 0.00099» и т.д.
Пример:
# 2 равно 2, равно 1(верно)
2 == 2
# 2 меньше 1, равно 0(неверно)
2 < 1
# 3 неравно 3, равно 0(неверно)
3 != 3
Логические операторы:
Логические операторы сами по себе мало что значат, однако при использовании в связке с операторами сравнения они становятся мощным инструментом для создания условий. Эти операторы используются для создания более сложных схем таких как проверка находится ли значение в определённых пределах. Примерное применение для операторов сравнения - они становятся верными (1) или ложными (0), или же они вводят ложь (0) или правду (не ноль)
B & B : и, если всё верно, то правда, иначе ложь.
В | B : или, что либо верно, то правда, иначе ложь
!B : не, равнозначно «B==0», отрицает логическое значение "2 => 0", "1 => 0", "0 => 1"
Примеры:
# 1 и 0, = 0
1 & 0
# 2 или 0, = 2
2 | 0
# 3 больше 5 или 4 меньше 2, = 0
3 > 5 | 4 < 2
Условное выражение:
Условное выражение будет выполнять одно из двух выражений в зависимости от значения условия, это позволяет вам делать одни вычисления в одном случае и другие вычисления в другом.
(B ? T : F) : если В верно, то выполнить Т, иначе выполнить F
Вниамение! Учтите, что вы должны выражать это в скобках, иначе, вероятней всего, вы получите ошибку.
# Эти две записи имеют одинаковую функциональность
Out = (A < 0 ? 1 : 0)
Out = (A < 0)
Примеры:
# Если А больше 32, то выполнить 32, иначе выполнить А, получившееся назначить на Out
Out = (A > 32 ? 32 : A)
# Если Select верен, то выполнить А+В, иначе выполнить В+С, затем прибавить к этому 2 и назначить получившееся на Out
Out = 2 + (Select ? A + B : B + C)
(в данном случае A+B выполнится только если Select = 1 а B+C выполнится только если Select=0)
Условные операторы:
Несмотря на удобность условного выражения, условные операторы могут быть очень полезными, они позволяют контролировать выполнение кода, тоесть только определеные части кода будут запущены при выполнении определенного условия. Или же для остановки выполнения если предварительное условие не удовлетворено.
Примеры:
B -> E; : если В верно, то выполнить Е
B -> E1, E2, ...; : если В верно, то выполнить Е1, затема Е2
B -> E1 E2 ...; : альтернативный тип записи для предыдущего
В формулировке условия вы можете так же написать «end», что остановит дальнейшее выполнение
B -> end; : если В верно, то остановить выполнение
B -> E, ..., end; : если В верно, то выполнить Е и остановить выполнение.
B -> E ... end; : альтернативный тип записи для предыдущего
Внимание! запомните, что в конце каждой формулировки условия должна стоять точка с запятой «;»
Примеры:
# Если Reset верно, то перезапустить Clock, всегда увеличивающийся на 1
Reset -> Clock = 0; Clock += 1
# Если Active верно и Angle больше 0, то присвоить TrustUp'у 1 и TrustDown’у 0
Active & Angle > 0 -> ThrustUp = 1, ThrustDown = 0;
# Если Tick не запущен и верен, то остановить выполнение, иначе увеличить Count на 1.
!(~Tick & Tick) -> end; Count += 1
Последовательность:
Как вы только что видели в условных операторах, возможно составлять разнообразные выражения путём разделения их пробелом или запятой, и это верно не только для условных операторов.
E1 E2 ... : выполнить Е1, затем Е2
E1, E2, ... : альтернативный тип записи для предыдущего
Пример:
# Присвоить 1 к А, 2 к В
A = 1, B = 2
Пакеты:
Несмотря на то, что в вайрмоде имеется поддержка пакетов с помощью чипов duplexer, экспрешн чип использует иную технику, ни как не связаную с чипом duplexer. Причина этого в том, что они могу послужить причиной серьезной путаницы если вы неправильно их соедините и не отладите, так что вы можете создать свой собственный экспрешн дюплексер (правильней называть мультиплексер/демультеплексер) используя этод метод.
Для того, чтобы послать пакет (несколько значений) по одному каналу вы назначаете выход (Packet) в отсылающем чипе и задаёте каманду «Packet = send(A, B, B + A)», вы можете назначить любое количество значений для функции.
Для того, чтобы получить пакет, вы назначаете вход (Packet) в получающем чипе и задаёте команду «B = recv(Packet, 2)», что означает что вы извлекаете аргумент под номером 2 из пакета, который в этом случае явяляется значением В, выходящий за установленные рамки показатель будет принимать значение «-1»
Пример:
# Посылающий (выход: Packet), отсылает 3 значения (1: A, 2: B, 3: B + A)
Packet = send(A, B, B + A)
# Принимающий (вход: Packet), извлекает значение из пакета (2:
B = recv(Packet, 2)
Это правильное применение, и нет ничего что бы мешало вам созданию различных портов пакетов в экспрешн чипе, но знайте что вы не может посылать несколько пакетов по одному каналу в течении одного выполнения – прибудет последний отосланый пакет.
Внимание! Это эксперементальные функции, и их следует использовать с осторожностью, хотя сама функциональность не вызывает проблем, некоторые установки склонны к определённым проблемам относительно функционирования чипов, вам следует четко понимать оператор «~» и прочитать раздел «Специальные операторы» перед попыткой создать мультиплексер/демультиплексер-экспрешн чип.
Пакеты не будут оставаться все время, а будут переписаны другими пакетами, важно, что вы прочтёте пакет при его отправлении (запомните: «~» - прочесть когда приведено в действие), и не пытаться прочесть его потом. Чипы, которые вводят лишь 1 пакет не склонны к каким либо подобным проблемам и могут быть использованы без каких либо посторонних действий, если же вы имеете несколько каких либо входов, вы должны использовать включающий механизм («~»)
Подходящее применение для этих функций - это упростить процесс передачи информации для индивидуальных систем так, чтобы свести вайр-соединения к минимуму и сделать возможным простое соединение, отсоединение и восстановление, создание систем, основанных на каких либо событиях или модульных систем, возможно, некоторое подобие центрального узла, ответственного за сообщения и и/или распределяющего сообщения по подсоединённым узлам. Я настоятельно не рекомендую использовать это с выходами векторов и тому подобного для созданимя чеголибо хорошего или точного, это лишь принесёт вам головную боль.
Примеры:
# Multiplexer: (входы: A B C D, выходы: Packet)
Packet = send(A, B, C, D)
# Demultiplexer (входы: Packet, выходы: A B C D)
A = recv(Packet, 1)
B = recv(Packet, 2)
C = recv(Packet, 3)
D = recv(Packet, 4)
Внутренние часы:
Внимание! Это эксперементальная функция которая может быть изменена в случае необходимости.
До сих пор возможно было создавать комплексные циклы, которые могли бы задерживать события или на протяжении какого либо времени изменяться, только с помощью внешних часов (таймеров). Теперь появилась возможность убрать те внешние часы (таймеры) и создавать более сложные установки.
Вместо того чтобы высчитывать частоту, для того чтобы предугадать когда произойдет следующее вычисление, используйте "schedule(50)" это заставит выполнить вычисление только через 50 мили секунд (задержка вычислений) а чтобы неприрывно выдерживать задержку перед каждым вычисленем вы должны в начале кода написать "interval(50)"
Также существует возможность задержать выполнение кода с реакцией на нажатие кнопки, например "~Boom & Boom -> schedule(1000);" эта строка заставит выполнится код через 1 секунду после нажатия на кнопку. Минимальная задержка – 20 милисекунд. Значения меньше 20 будут автоматически изменены на 20, а значения равные 0 автоматически отменят любые задержки. Чтобы знать является ли даннове вычисление результатом работы внутренних часов(таймера) была введена функция “clk()” и она будет возвращать значение «1» если текущее вычисление является результатом работы внутренних часов (таймера) и «0» если нет.
Примеры:
# Считает по 1 секунде с момента спавна.
interval(1000)
clk() -> Seconds += 1;
# Выключатель для динамита с задержкой на 5 секунд.
~Boom & Boom -> schedule(5000);
clk() -> Explode = 1;
Внимание! Вы не можете задать несколько интервалов внутренних часов(таймеров) последний перепишет предыдущий:
interval(20)
interval(1000)
в итоге интервал выполнения составит 1 секунду.
Специальные Операторы:
Чтобы вы могли создавать еще более сложные установки, включена поддержка еще 2х операторов:
~V : верно(1) если переменная была изменена(в основном для переменных входа)
$V : вычисляет дельту (разница между текущим состоянием переменной и ее предыдущем состоянием)
И Вы конечно задаетесь вопросом, для чего мне понадобится "~" для начала, если Вы не используете переменные, которые сохраняются между каждым выполнением, вам не понадобится "~", но если вы используете переменные, которые сохраняются между каждым выполнением это могло бы помочь вам решать некоторые проблемы, потому что Вы должны помнить, что вычисления в эксп. Чипе запускаются каждый раз при изменении входа.
Примеры:
# Если Tick Включили то к Clock прибавить 1
~Tick & Tick -> Clock += 1;
# высчитывает силу трастера используя дельту чтобы избежать тряски, и нормализует срыв на границе угла при переходе от 180о к -180о
Thrust = angnorm(Angle + $Angle * 2)
Дополнительные Примечания:
Внимание! Если вы пишете код эксп. Чипа вне игры то будьте осторожны, длинна одной строки не должна привышать 92 символа, иначе весь код может быть не принят сервером вызывая странные ошибки.
Если поместить в начале строки «#» то эта строка станет коментарием и не будет выполняться.
Также вы можете разбивать код на несколько линий, например если предидущая строка не заканчивается то следующая строка будет выполняться вместе с предыдущей.
Пример:
A = 4 +
3 / 2 –
1 +
2
# тоже самое что и
A = 4 + 3 / 2 - 1 + 2
Запомните, гораздо легче использовать несколько эксп.чипов чем 1, например если требуется выполнение нескольких функций(имеется ввиду если выполняются разные задачи) то лучше разбить их на несколько чипов. (например не делать в 1 чипе и пушку и механизм управления машиной)
Привет Мир:
Привет мир это пример для новичков, если вы хотите посмотреть как он работает просто загрузите этот код в эксп.чип и подключите все входы и выходы.
(папка с сохраненными кодами эксп.чипов находится по адресу:
…\SteamApps\*имя_вашего_аккаунта*\garrysmod\garrysmod\data\ExpressionGate)
N@Hello World
I@Value
O@Value Squared SquareRoot Positive
Squared = Value * Value
SquareRoot = sqrt(Value)
Positive = Value >= 0
Тикающие часы:
1. Подключите чип Pulser (TickTime = 1) к Tick
2. Подключите кнопку к Step, число на кнопке будет влиять на увеличение часов за 1 тик.
3. Подключите кнопку к Reset.
4. Подключите экраны к 3-м выходам чтобы посмотреть результат, или используйте дебагер
N@Clock
I@Tick Step Reset
O@Hours Minutes Seconds
~Tick & Tick -> Clock += 1;
~Step & Step -> Clock += Step;
~Reset & Reset -> Clock = 0;
Seconds = Clock % 60
Minutes = floor(Clock / 60) % 60
Hours = floor(Clock / 3600) % 24
3D голографическая сфера:
1: подключите кнопку к On
2: подключите кнопку к Reset
3: подключите входы (X, Y, Z) холо эмитера к одноименным выходам чипа
N@HoloSphere
I@On Reset
O@X Y Z
interval(40)
N += (On?10:0)
R = N * 6/ 360 *
N=(Reset?0:N)
R=(Reset?0:R)
R > 180 -> N = 0;
X = sin® * sin(N) * 30
Y = sin® * cos(N) * 30
Z = cos® * 30
Перечень функций:
Обычные:
abs(n) : Абсолютное значение (всегда положительное)
clamp(n, l, u) : возвращает значение с менимальной и максимальной границей "max(l, min(u, n))"
frac(n) : Фракционная часть(???)
int(n) : Часть целого числа
Математические:
e() : Математическая константа e (2.71828183)
exp(n) : e to the power of n (???)
ln(n) : Логарифм с основой e
log(n, k) : Логарифм с основой k
log2(n) : Логарифм с основой 2
log10(n) : Логарифм с основой 10
mod(n,c) : Matematical modulo, "mod(-3, 2) = -1" (???)
sgn(n) : знак числа, "n < 0 => -1", "n == 0 => 0", "n > 0 => 1"
cbrt(n) : кубический корень
sqrt(n) : квадратный корень
root(n, k) : корень N-степени
Округление:
ceil(n) : округление до следующего целого числа
ceil(n, d) : округление до следующего целого числа с десятичной точностью
floor(n) : округление до предыдущего целого числа
floor(n, d) : округление до предыдущего целого числа с десятичной точностью
round(n) : округление до ближайшего целого числа
round(n, d) : округление до ближайшего целого числа с десятичной точностью
Выбор:
max(n, ...) : возвращает значение максимального числа
min(n, ...) : возвращает значение минимального числа
avg(n, ...) : возвращает среднее значение чисел
sel(i, n, ...) : Returns the i:th value (???)
Рандом:
curtime() : Текущее время
random() : Случайное значение от 1 до 0
random(l, u) : значение от l до u
Тригонометрические:
acos(x) : Арк косинус
asin(x) : Арк синус
atan(x) : Арк тангенс
atan(y, x) : Арк тангенс x/y
atan2(y, x) : Alias for atan(y, x) (???)
cos(d) : косинус
cosh(d) : Гиперболический косинус
sin(d) : синус
sinh(d) : Гиперболический синус
tan(d) : Tangent
tanh(d) : Гиперболический тангенс
angnorm(d) : Нормализует угол, "-180 =< угол < 180"
deg(n) : конвертирует радианы в градусы
rad(n) : конвертирует градусы в радианы
pi() : Константа Пи (3.14159265)
Поддержка векторов:
Внимание! Эти функции очень сложны для новичков.
vector(x, y, z) : Создает вектор для использования с этими функциями
vecx(v) : X координата вектора
vecy(v) : Y координата вектора
vecz(v) : Z координата вектора
vecpitch(v) : Pitch вектора
vecyaw(v) : Yaw вектора
veclength(v) : длинна вектора
vecnormalize(v) : нормализует вектор
vecadd(v1, v2) : сложение 2-х векторов
vecsub(v1, v2) : вычитание 2-х векторов
vecmul(v1, v2) : умножение 2-х векторов
vecsmul(v1, n) : умножение вектора на число
vecsdiv(v1, n) : деление вектора на число
vecdot(v1, v2) : Dot product of two vectors (???)
veccross(v1, v2) : Cross product of two vectors (???)
vecdistance(v1, v2) : дистанция между двумя векторами
vecrotate(v, p, y, r) : повернуть вектор используя pitch, yaw и roll
Специальные:
first() : равно 1 если чип сброшен(reset)
send(n, ...) : Создает пакет
recv(id, i) : Извлекает значение из пакета
clk() : Пульс часов произошел
interval(ms) : вспомогательная функция задержки
schedule(ms) : Задержка следующего пулься часов(таймера)