PDA

Просмотр полной версии : [Include] random switch



vvw
13.11.2019, 00:44
Данная библиотека позволяет создавать random switch, который выбирает одно из существующих значений.

Исходный код:

#include <amx/amx_base>
stock _random_switch()
{
static base_addr = 0, temp_addr, randvalue;
if (0 == base_addr) {
base_addr = GetAmxBaseAddress();
} {}

#emit load.s.alt 4
#emit lctrl 0
#emit add
#emit move.alt
#emit lctrl 1
#emit sub.alt
#emit add.c 4
#emit stor.pri temp_addr
#emit lref.pri temp_addr
#emit stor.pri temp_addr

#emit load.alt base_addr
#emit lctrl 1
#emit add
#emit move.alt
#emit load.pri temp_addr
#emit sub
#emit add.c 4
#emit stor.pri temp_addr
#emit push.pri
#emit lref.pri temp_addr
#emit stor.pri temp_addr

randvalue = random(temp_addr) + 1;

#emit load.pri randvalue
#emit shl.c.pri 3
#emit pop.alt
#emit add
#emit stor.pri temp_addr
#emit lref.pri temp_addr
#emit retn
return 0;
}

#define switchrand() \
switch(_random_switch())


Использование:

main()
{
switchrand() {
case 123: {printf("123\n");}
case 321: {printf("321\n");}
default: {} // не учитывает
}
}



Идея: DeimoS
Реализация: VVWVV

P.S.: код скорее всего будет обновлен, ибо я отвык от AMX инструкций.

Daniel_Cortez
14.11.2019, 11:58
Задумка хорошая, но хотелось бы отметить несколько моментов:
В текущем виде пример использования только объясняет, что такое random switch , но не где это может пригодиться. Очень не помешал бы более-менее реалистичный пример.
DeimoS ?
Если не сейчас, то в будущем код следует перевести на __emit. Если сейчас выгода не так очевидна, то в следующем релизе компилятора с новыми псевдоинструкциями можно будет здорово упростить код: например, псевдоинструкции load.u.pri/alt можно скармливать целые выражения, причём не только константные, но и вообще любые, даже с вложенным использованием __emit.
Вместо

#emit stor.pri temp_addr
#emit lref.pri temp_addr
#emit stor.pri temp_addr

можно было сделать

#emit load.i
#emit stor.pri temp_addr

Нельзя ли как-нибудь обойтись без функции GetAmxBaseAddress() (а заодно и выкинуть зависимость от amx_assembly, откуда используется только эта единственная функция)?
Да и вообще, зачем здесь нужен базовый адрес AMX? Можно же просто получить смещение секции кода относительно секции данных (lctrl 1, move.alt, lctrl 0, sub), затем к нему прибавить адрес возврата из функции - по этому адресу и будет располагаться инструкция switch.

vvw
14.11.2019, 12:31
Задумка хорошая, но хотелось бы отметить несколько моментов:[list]
В текущем виде пример использования только объясняет, что такое random switch , но не где это может пригодиться. Очень не помешал бы более-менее реалистичный пример.
DeimoS ?

Я надеюсь, что DeimoS добавит примеры.


Если не сейчас, то в будущем код следует перевести на __emit. Если сейчас выгода не так очевидна, то в следующем релизе компилятора с новыми псевдоинструкциями можно будет здорово упростить код: например, псевдоинструкции load.u.pri/alt можно скармливать целые выражения, причём не только константные, но и вообще любые, даже с вложенным использованием __emit.

Когда будет реализ - сделаю.


Вместо

#emit stor.pri temp_addr
#emit lref.pri temp_addr
#emit stor.pri temp_addr

можно было сделать

#emit load.i
#emit stor.pri temp_addr


Была бы ошибка, не?


Нельзя ли как-нибудь обойтись без функции GetAmxBaseAddress() (а заодно и выкинуть зависимость от amx_assembly, откуда используется только эта единственная функция)?
Да и вообще, зачем здесь нужен базовый адрес AMX? Можно же просто получить смещение секции кода относительно секции данных (lctrl 1, move.alt, lctrl 0, sub), затем к нему прибавить адрес возврата из функции - по этому адресу и будет располагаться инструкция switch.


Вот алгоритм получения адреса инструкции switch:

#emit load.s.alt 4
#emit lctrl 0
#emit add
#emit move.alt
#emit lctrl 1
#emit sub.alt
#emit add.c 4
#emit stor.pri temp_addr
#emit lref.pri temp_addr
#emit stor.pri temp_addr


base_addr используется для нахождения относительного адреса из switch.



switch <абсолютный адрес на casetbl>
jump
...
casetbl <кол-во кейсов> <адрес по-умолчанию>


Чуть позже я перепишу без зависимостей.



P.S.: код скорее всего будет обновлен, ибо я отвык от AMX инструкций.

DeimoS
14.11.2019, 14:25
Из более простых и очевидных примеров -собственно, любые системы, где нужно выбрать рандомно один из элементов switch.
Например, решили мы сделать пикап, который бы выдавал рандомный приз:
switch(random(6))
{
case 0:
{
new money = random(4000)+1000;
GivePlayerMoney(playerid, money);
new string[14+4+1];
format(string, sizeof(string), "Вы получили $%d.", money);
SendClientMessage(playerid, -1, string);
}
case 1:
{
new money = random(2000)+1000;
GivePlayerMoney(playerid, -money);
new string[14+4+1];
format(string, sizeof(string), "Вы потеряли $%d.", money);
SendClientMessage(playerid, -1, string);
}
case 2:
{
#if !defined FLOAT_INFINITY
const Float:FLOAT_INFINITY = Float:0x7F800000;
#endif
SetPlayerHealth(playerid, FLOAT_INFINITY);
SendClientMessage(playerid, -1, "Вы получили бессмертие (до перезахода).");
}
case 3:
{
GivePlayerWeapon(playerid, WEAPON_DEAGLE, 300);
SendClientMessage(playerid, -1, "Вы получили пистолет.");
}
case 4:
{
new Float:x, Float:y, Float:z, Float:a;
GetPlayerPos(playerid, x, y, z);
GetPlayerFacingAngle(playerid, a);

new vehicleid = CreateVehicle(562, x, y, z, a, -1, -1, 1*60);
SetVehicleParamsEx(vehicleid,
VEHICLE_PARAMS_ON, VEHICLE_PARAMS_OFF, VEHICLE_PARAMS_OFF,
VEHICLE_PARAMS_OFF, VEHICLE_PARAMS_OFF, VEHICLE_PARAMS_OFF,
VEHICLE_PARAMS_OFF);
PutPlayerInVehicle(playerid, vehicleid, 0);
SendClientMessage(playerid, -1, "Вы получили а-а-а-втомобиль.");
}
case 5:
{
new Float:x, Float:y, Float:z, Float:a;
GetPlayerPos(playerid, x, y, z);
GetPlayerFacingAngle(playerid, a);

new vehicleid = CreateVehicle(562, x, y, z, a, -1, -1, 1*60);
SetVehicleParamsEx(vehicleid,
VEHICLE_PARAMS_ON, VEHICLE_PARAMS_OFF, VEHICLE_PARAMS_OFF,
VEHICLE_PARAMS_OFF, VEHICLE_PARAMS_OFF, VEHICLE_PARAMS_OFF,
VEHICLE_PARAMS_OFF);
PutPlayerInVehicle(playerid, vehicleid, 0);
SendClientMessage(playerid, -1, "Вы получили а-а-а-втомобиль.");
}
}
И всё, вроде бы, хорошо, система работает, а игроки довольны. Но тут мы, в одном из обновлений, решили убрать элемент под номером 1, ибо игроки расстраиваются, когда у них отбирают деньги. Если мы решим просто удалить его - система, фактически, станет нерабочей, ибо "random" иногда будет выдавать единицу. Чтоб всё исправить, придётся сделать что-то одно:
1) Добавить первый элемент на какой-то из существующих призов (то бишь, сделать "case 0, 1:"), но это, фактически, увеличит шанс выпадения того или иного приза.
2) Добавить какой-то новый приз вместо удалённого (придётся думать, что, естественно, большущий минус :mamba:)
3) Сместить номера всех элементов, которые шли после единицы + исправить значение в "random"
4) Придумать костыль, по типу добавления goto, который бы отправлял обработчик в позицию перед switch, дабы random сгенерировало новое число.

И если на подобном switch с малым количеством элементов все перечисленные пункты могут показаться незначительными, то представьте, что switch у вас состоит из 50, 100 и т.д. элементов. В этом случае switchrand довольно сильно упростит поддержку кода, сняв с вас и нужду думать насчёт того, чтоб все элементы были по порядку, и нужду в изменении значения, указанного в "random" при изменении количества элементов.


Более специфичным примером будет пример, схожий с тем, что уже предоставлен в шапке темы: когда в switch находятся значения с большим разбросом, а не идут по порядку.
Например, мы делаем RP мод с системой фракций и нам нужно отправить игрока в одну из трёх армий после выполнения одного из квестов. При этом, так получилось, что ID армий в списке фракций - 2, 15, 22.
И тут есть 2 пути решения без switchrand:
1) Поместить все нужные данные в массивы, определять рандомно индекс и уже отображать игроку нужную информацию из массивов согласно получившемуся индексу (но если у нас для каждой отдельной фракции нужно отображать уникальный код, могут возникнуть проблемы. Да и в целом код получится довольно сложным и менее гибким, относительно switchrand).
2) Сделать нечто подобное:
new army_frac[] = {2, 15, 22};
new fid = random(sizeof(army_frac));
switch(fid)
{
case 2:
{

}
case 15:
{

}
case 22:
{

}
}
Но тут придётся следить за тем, чтоб значения в массиве и номера элементов совпадали, что может быть проблемно (особенно в моменты когда элементы наполнятся кодом и/или количество элементов возрастёт).


В общем, главная задача switchrand - упростить работу со связкой "random+switch", перекладывая слежение за номерами элементов и числом в "random" со своей головы на компилятор. С switchrand единственное требование к коду будет заключаться в том, чтоб все элементы были уникальными (собственно, как и в switch). А уже по порядку они идут или имеются пробелы между значениями - не будет критично.
Надеюсь, изложил понятно основную задумку всего этого.

vvw
14.11.2019, 17:46
Кстати, может кто-нибудь проверить работоспособность данной библиотеки с плагином JIT?

Daniel_Cortez
29.11.2019, 19:36
Кстати, может кто-нибудь проверить работоспособность данной библиотеки с плагином JIT?
Не работает и в текущем виде работать не сможет. Функция _random_switch() узнаёт адрес инструкции switch (а по ней и таблицы переходов) по адресу возврата из функции, но проблема в том, что в качестве адреса возврата JIT передаёт не "виртуальный" адрес в секции кода, а физический адрес в сгенерированном нативном коде. И если виртуальный адрес можно преобразовать в физический (загрузить адрес в PRI, затем выполнить lctrl 8), то средств для преобразования физического адреса в виртуальный не предусмотрено.
Единственный способ обойти это ограничение - в месте вызова функции получить значение CIP (lctrl 6) и затем его передать в _random_switch(). Но #emit из макроса не поюзаешь, только оператор __emit, а значит придётся пожертвовать совместимостью со стоковым компилятором (впрочем, кто им сейчас пользуется?)

На данный момент я сделал это и ещё несколько изменений (немного изменил принцип работы функции, убрал зависимость от amx_assembly) и хочу выложить всё в виде готового инклуда. Каким образом мне записывать тебя в копирайтах? ("VVWVV"? Настоящее имя?) И ты не против будешь, если я выложу код под лицензией zlib?

SteveStage
01.12.2019, 18:08
Кстати, хотелось бы узнать насчет данной строчки

#include <amx/amx_base>

Это как? Два разных инклуда подключаются через слэш... Мб я не догоняю, но откуда такой инклуд?

P.S. Как я понял, эта строка подключается к этим инклудам: https://pro-pawn.ru/showthread.php?15075

DeimoS
01.12.2019, 18:40
Кстати, хотелось бы узнать насчет данной строчки

#include <amx/amx_base>

Это как? Два разных инклуда подключаются через слэш... Мб я не догоняю, но откуда такой инклуд?

P.S. Как я понял, эта строка подключается к этим инклудам: https://pro-pawn.ru/showthread.php?15075

Это не два разных инклуда, а папка, в которой находится инклуд, и сам файл. Ты что, никогда не видел как путь до файлов прописывается в адресной строке?

SteveStage
01.12.2019, 18:55
Ты что, никогда не видел как путь до файлов прописывается в адресной строке?

Я видел путь к файлу в адресной строке, но не знал, что также можно и с инклудами

vvw
02.12.2019, 09:15
Не работает и в текущем виде работать не сможет. Функция _random_switch() узнаёт адрес инструкции switch (а по ней и таблицы переходов) по адресу возврата из функции, но проблема в том, что в качестве адреса возврата JIT передаёт не "виртуальный" адрес в секции кода, а физический адрес в сгенерированном нативном коде. И если виртуальный адрес можно преобразовать в физический (загрузить адрес в PRI, затем выполнить lctrl 8), то средств для преобразования физического адреса в виртуальный не предусмотрено.
Единственный способ обойти это ограничение - в месте вызова функции получить значение CIP (lctrl 6) и затем его передать в _random_switch(). Но #emit из макроса не поюзаешь, только оператор __emit, а значит придётся пожертвовать совместимостью со стоковым компилятором (впрочем, кто им сейчас пользуется?)

Совсем забыл про это, спасибо.


На данный момент я сделал это и ещё несколько изменений (немного изменил принцип работы функции, убрал зависимость от amx_assembly) и хочу выложить всё в виде готового инклуда. Каким образом мне записывать тебя в копирайтах? ("VVWVV"? Настоящее имя?) И ты не против будешь, если я выложу код под лицензией zlib?

Да, можешь написать "VVWVV". zlib - хорошая лицензия