PDA

Просмотр полной версии : [Function] ResetPlayerWeapSlot



vovandolg
28.07.2016, 04:38
Описание:

Функция (ResetPlayerWeaponSlot) удаляющая оружие с выбранного слота

Наверное все замечали что в SA:MP нету функции удаления оружия с выбранного слота,
или может замечали такой момент когда истратив весь б/к в GetPlayerWeaponData
нагло показывает что у Вас есть оружие, а патронов к нему "тю-тю",
баг? или фича такая чтобы патроны потом пополнять? каждый по разному на это посмотрит.

ResetPlayerWeaponSlot возвращает 2 значения(0,1)
0 - Игрок не в сети
1 - Слот успешно обнулён

Код:


#define MAX_SLOT_WEAP (13) //макс. оружейных слотов

stock ResetPlayerWeaponSlot(playerid, slot) //[slot 0-12]
{
if(IsPlayerConnected(playerid) == 0) return 0;
goto no_init_arrays;
new weapon[MAX_SLOT_WEAP], ammo[MAX_SLOT_WEAP], i, _value;
no_init_arrays:
for(i = 0; i < MAX_SLOT_WEAP; i++)
{
if(i != slot)
{
GetPlayerWeaponData(playerid, i, weapon[i], ammo[i]);
}
}
ResetPlayerWeapons(playerid);
for(i = 0; i < MAX_SLOT_WEAP; i++)
{
if((_value = ammo[i]) != 0)
{
GivePlayerWeapon(playerid, weapon[i], _value);
}
}
return 1;
}


Пример использования:


CMD:resetslot(playerid, params[])
{
new gunslot;
if(sscanf(params, "d", gunslot))
return SendClientMessage(playerid, -1, !"Использование: /resetslot [slot 0-12]");
if(ResetPlayerWeaponSlot(playerid, gunslot) == 0)
return SendClientMessage(playerid, -1, !"Игрок не в сети!");
SendClientMessage(playerid, -1, !"Выбранный слот был обнулён!");
return 1;
}


Автор темы:

vovandolg

Помогали:

VVWVV, Anton Styazhkin

VVWVV
28.07.2016, 05:12
- Вы не указали проверку на нахождения игрока в игре (IsPlayerConnected), а это означает, что все циклы, массивы будут инициализироваться/итерироваться без участия игрока.
- Стоило бы вынести переменную "sl" из циклов, ибо переменная инициализируется несколько раз. (Зачем так называть переменную?)
- Доступ к элементу массива очень затратный, поэтому стоило бы кешировать значение.

Пример моей функции:


stock
ResetPlayerWeaponSlot(playerid, slot)
{
if (IsPlayerConnected(playerid) == 0)
return 0;
goto no_init_arrays;
new weapon[MAX_WEAPON_SLOTS], ammo[MAX_WEAPON_SLOTS], i, _value;
no_init_arrays:
for (i = 0; MAX_WEAPON_SLOTS; ++i)
{
if (i == slot)
continue;
GetPlayerWeaponData(playerid, i, weapon[i], ammo[i]);
}
ResetPlayerWeapons(playerid);
for (i = 0; i != MAX_WEAPON_SLOTS; ++i)
{
if ((_value = ammo[i]) == 0)
continue;
GivePlayerWeapon(playerid, weapon[i], _value);
}
return 1;
}

ziggi
28.07.2016, 09:34
Пару циклов на 13 итераций и 3 разных функции нам в помощь(ибо по другому не как):

Ошибаешься, в fixes.inc эту проблему решили следующим образом:

stock FIX_GetPlayerWeaponData(playerid, slot, &weapons, &ammo)
{
return
slot = GetPlayerWeaponData(playerid, slot, weapons, ammo),
weapons = ammo ? weapons : 0,
slot;
}
#if defined _ALS_GetPlayerWeaponData
#undef GetPlayerWeaponData
#else
#define _ALS_GetPlayerWeaponData
#endif

#define GetPlayerWeaponData FIX_GetPlayerWeaponData

То есть при вызове GetPlayerWeaponData проверяется наличие патронов и, если их 0, то weaponid передаётся в виде 0. Как мне кажется, так гораздо проще и эффективнее.

vovandolg
28.07.2016, 15:31
- Вы не указали проверку на нахождения игрока в игре (IsPlayerConnected), а это означает, что все циклы, массивы будут инициализироваться/итерироваться без участия игрока.

Я привык что у меня все такие проверки в команде или ещё где до работы этого кода, ну да ладно щас черканём..



- Стоило бы вынести переменную "sl" из циклов, ибо переменная инициализируется несколько раз. (Зачем так называть переменную?)

Вот тут я не понял ты к моему невинному коду придрался:beee:
sl - slot сокращённо, так я делал и мне так удобнее видеть было =3



- Доступ к элементу массива очень затратный, поэтому стоило бы кешировать значение.

Пример моей функции:


stock
ResetPlayerWeaponSlot(playerid, slot)
{
if (IsPlayerConnected(playerid) == 0)
return 0;
goto no_init_arrays;
new weapon[MAX_WEAPON_SLOTS], ammo[MAX_WEAPON_SLOTS], i, _value;
no_init_arrays:
for (i = 0; MAX_WEAPON_SLOTS; ++i)
{
if (i == slot)
continue;
GetPlayerWeaponData(playerid, i, weapon[i], ammo[i]);
}
ResetPlayerWeapons(playerid);
for (i = 0; i != MAX_WEAPON_SLOTS; ++i)
{
if ((_value = ammo[i]) == 0)
continue;
GivePlayerWeapon(playerid, weapon[i], _value);
}
return 1;
}


Можно ссылочку где про кеширование почитать можно?)


Ошибаешься, в fixes.inc это проблему решили следующим образом:

stock FIX_GetPlayerWeaponData(playerid, slot, &weapons, &ammo)
{
return
slot = GetPlayerWeaponData(playerid, slot, weapons, ammo),
weapons = ammo ? weapons : 0,
slot;
}
#if defined _ALS_GetPlayerWeaponData
#undef GetPlayerWeaponData
#else
#define _ALS_GetPlayerWeaponData
#endif

#define GetPlayerWeaponData FIX_GetPlayerWeaponData

То есть при вызове GetPlayerWeaponData проверяется наличие патронов и, если их 0, то weaponid передаётся в виде 0. Как мне кажется, так гораздо проще и эффективнее.

Ну это как же я пропустил, то ли инклуд старый у меня...
По крайней мере слоты заданные не очищает даже если там есть оружие, поэтому моя функция ещё в силе =3

- - - Добавлено - - -

Ещё не понял момента в отрывке кода....
Зачем вызывать лишний раз оператора


if (i == slot)
continue;
GetPlayerWeaponData(playerid, i, weapon[i], ammo[i]);

Когда можно просто вот так сделать


if (i != slot)
GetPlayerWeaponData(playerid, i, weapon[i], ammo[i]);

DeimoS
28.07.2016, 21:39
Ещё не понял момента в отрывке кода....
Зачем вызывать лишний раз оператора


if (i == slot)
continue;
GetPlayerWeaponData(playerid, i, weapon[i], ammo[i]);

Когда можно просто вот так сделать


if (i != slot)
GetPlayerWeaponData(playerid, i, weapon[i], ammo[i]);


Либо ты вызываешь "continue" явно, либо делаешь это неявно - разницы не вижу

Desulaid
28.07.2016, 22:22
- Вы не указали проверку на нахождения игрока в игре (IsPlayerConnected), а это означает, что все циклы, массивы будут инициализироваться/итерироваться без участия игрока.
- Стоило бы вынести переменную "sl" из циклов, ибо переменная инициализируется несколько раз. (Зачем так называть переменную?)
- Доступ к элементу массива очень затратный, поэтому стоило бы кешировать значение.

Пример моей функции:


stock
ResetPlayerWeaponSlot(playerid, slot)
{
if (IsPlayerConnected(playerid) == 0)
return 0;
goto no_init_arrays;
new weapon[MAX_WEAPON_SLOTS], ammo[MAX_WEAPON_SLOTS], i, _value;
no_init_arrays:
for (i = 0; MAX_WEAPON_SLOTS; ++i)
{
if (i == slot)
continue;
GetPlayerWeaponData(playerid, i, weapon[i], ammo[i]);
}
ResetPlayerWeapons(playerid);
for (i = 0; i != MAX_WEAPON_SLOTS; ++i)
{
if ((_value = ammo[i]) == 0)
continue;
GivePlayerWeapon(playerid, weapon[i], _value);
}
return 1;
}


Зачем пропускать инициализацию, если ты не получишь профита, но сделаешь код для других запутанней?


Пропуск инициализации ячеек массива нулями.

При объявлении локальных массивов внутри функций компилятор не только резервирует место в стеке под массив, но и инициализирует все его ячейки нулями, чтобы в них не было мусорных значений.
Сделано это из-за быдлокодеров, которые, пользуются переменными, забывая их инициализировать.
Тем не менее, инициализация занимает некоторое время, да и польза от этого сомнительная, если вы способны отдавать отчёт своим действиям.
К счастью, есть способ избежать потерь во времени, пропустив инициализацию:


SomeFunction()
{
// перед объявлением массива перейдём на метку skip_array_init
goto skip_array_init;
// объявляем массив
new arr[256];
skip_array_init:
// в этом месте массив arr будет существовать, но он не будет инициализирован:
// код инициализации существует, но он находится _до_ метки skip_array_init,
// т.е. мы обошли этот код

// дальше - код с использованием массива
}

Скомпилируем код, а затем дизассемблируем его.
Получим следующую последовательность инструкций:


; Начало функции SomeFunction.
proc
; И сразу же - переход на метку l.0 (см. далее).
jump 0
; Выделение места в стеке под массив из 16 ячеек (ffffffc0 означает "-16")
; (этот код никогда не сработает, переход на l.0 был безусловным).
stack ffffffc0
; Заполнение массива нулями происходит побайтово,
; 16 ячеек - 64 байта (40 в шестнадцатеричной системе счисления).
zero.pri
addr.alt ffffffc0
fill 40
; Метка, с помощью которой пропускается инициализация массива.
l.0
; Установка вершины стека в то положение, когда выделено 16 ячеек под массив,
; Обратите внимание: в отличие от обычного создания массива,
; здесь массив не заполняется никакими значениями,
; происходит только резервирование пространства,
lctrl 5
add.c ffffffc0
sctrl 4
; Код с использованием массива.
; ...
; Высвобождение пространства в стеке и возврат из функции.
stack 40
zero.pri
retn

Если кому интересно, вот тест скорости:


#include <a_samp>

const PROFILE_ITERATIONS_MAJOR = 1000;
const PROFILE_ITERATIONS_MINOR = 1000;

const STRING_SIZE = 512;


const SZ = STRING_SIZE;

#define _m1();\
{new a[SZ];}
#define method1();\
_m1();_m1();_m1();_m1();_m1();_m1();_m1();_m1();_m1();_m1();

#define _m2();\
{goto skip_init;new a[SZ];skip_init:}
#define method2();\
_m2();_m2();_m2();_m2();_m2();_m2();_m2();_m2();_m2();_m2();


bool:IsJITEnabled()
{
#emit lctrl 7
#emit retn
return true;
}

main()
{
printf(
"Testing array initialization..." "\n"\
"(%dx%d iterations, array size: %d, JIT status: %sabled)",
PROFILE_ITERATIONS_MAJOR, PROFILE_ITERATIONS_MINOR,
STRING_SIZE, IsJITEnabled() ? ("en") : ("dis")
);
new t1, t2, t, i, j;
t1 = 0, t2 = 0;
for(i = 0; i < PROFILE_ITERATIONS_MAJOR; ++i)
{
t = GetTickCount();
for(j = 0; j < PROFILE_ITERATIONS_MINOR; ++j)
{
method1();
method1();
method1();
method1();
method1();
method1();
method1();
method1();
method1();
method1();
}
t1 += GetTickCount()-t;
t = GetTickCount();
for(j = 0; j < PROFILE_ITERATIONS_MINOR; ++j)
{
method2();
method2();
method2();
method2();
method2();
method2();
method2();
method2();
method2();
method2();
}
t2 += GetTickCount()-t;
}
printf(
"method 1: %d" "\n"\
"method 2: %d",
t1, t2
);
}

Макросы method1 и method2 сделаны, чтобы повторить в них создание локальных массивов по 10 раз.
Кроме того сами макросы используются в функции main по 10 раз (итого получается 10x10 = 100 повторов).
Это не что иное, как развёртывание циклов. Я сделал именно так, чтобы уменьшить погрешность от циклов и вызовов GetTickCount, таким образом, увеличив точность измерений.
Также в процессе компиляции выдаётся куча варнингов, но они, опять же, из-за макросов, не обращайте на них внимания.
Тест я запускал на массивах из 32, 256 и 512 ячеек, с включенным и выключенным JIT.
Результаты следующие:



Testing array initialization...
(1000x1000 iterations, array size: 32, JIT status: disabled)
method 1: 5581
method 2: 47

Testing array initialization...
(1000x1000 iterations, array size: 32, JIT status: enabled)
method 1: 2640
method 2: 4




Testing array initialization...
(1000x1000 iterations, array size: 256, JIT status: disabled)
method 1: 31324
method 2: 42

Testing array initialization...
(1000x1000 iterations, array size: 256, JIT status: enabled)
method 1: 15106
method 2: 3




Testing array initialization...
(1000x1000 iterations, array size: 512, JIT status: disabled)
method 1: 63840
method 2: 40

Testing array initialization...
(1000x1000 iterations, array size: 512, JIT status: enabled)
method 1: 29161
method 2: 4


Из измерений можно сделать вывод, что размер массива никак не влияет на результаты метода с пропуском инициализации массива.
Но всё же от трюка не сильно много пользы. Да, цифры в тесте, казалось бы, большие, но не стоит забывать, что они получены с помощью 100 миллионов итераций. В нормальных же условиях время инициализации массива в 512 ячеек составит всего ~0.0006384 мс или ~0.0000006 секунды.

vovandolg
28.07.2016, 22:51
Так вот тут не пойму,
no_init_arrays: будет пропускать инициализацию до ResetPlayerWeapons(playerid);
или до конца всей функции?

- - - Добавлено - - -

И зачем вот эта переменная _value

Desulaid
28.07.2016, 23:10
Так вот тут не пойму,
no_init_arrays: будет пропускать инициализацию до ResetPlayerWeapons(playerid);
или до конца всей функции?

- - - Добавлено - - -

И зачем вот эта переменная _value

1. Просто "перепрыгнем" через заполнение массива нулями. "Прыжок" буде с goto no_init_arrays; на "no_init_arrays:".
2. Обращаться к элементу массива затратно. Этого можно избежать, если при одном из вызовов сохранить значение элемента в переменную и дальше уже пользоваться переменной со значением элемента массива.

vovandolg
28.07.2016, 23:39
Ну и)) наинициализировались?
http://rgho.st/8QhhSJV2C/image.png

Desulaid
28.07.2016, 23:50
Ну и)) наинициализировались?
http://rgho.st/8QhhSJV2C/image.png

VVWVV просто забыл кое что добавить. Бывает)


stock
ResetPlayerWeaponSlot(playerid, slot)
{
if (IsPlayerConnected(playerid) == 0)
return 0;
goto no_init_arrays;
new weapon[MAX_WEAPON_SLOTS], ammo[MAX_WEAPON_SLOTS], i, _value;
no_init_arrays:
for (i = 0; != MAX_WEAPON_SLOTS; ++i)
{
if (i == slot)
continue;
GetPlayerWeaponData(playerid, i, weapon[i], ammo[i]);
}
ResetPlayerWeapons(playerid);
for (i = 0; i != MAX_WEAPON_SLOTS; ++i)
{
if ((_value = ammo[i]) == 0)
continue;
GivePlayerWeapon(playerid, weapon[i], _value);
}
return 1;
}

VVWVV
29.07.2016, 00:39
Зачем пропускать инициализацию, если ты не получишь профита, но сделаешь код для других запутанней?




Код не будет запутанным, ибо между оператором и меткой всего лишь одна строка.



Я привык что у меня все такие проверки в команде или ещё где до работы этого кода, ну да ладно щас черканём..


Вы можете забыть об этом.



Можно ссылочку где про кеширование почитать можно?)


google.com

vovandolg
29.07.2016, 01:11
VVWVV просто забыл кое что добавить. Бывает)

И я не увидел:black_eye:
_______________________
Обновил шапку темы, всем благодати за подробный разбор)

Daniel_Cortez
29.07.2016, 23:53
Ещё не понял момента в отрывке кода....
Зачем вызывать лишний раз оператора


if (i == slot)
continue;
GetPlayerWeaponData(playerid, i, weapon[i], ammo[i]);

Когда можно просто вот так сделать


if (i != slot)
GetPlayerWeaponData(playerid, i, weapon[i], ammo[i]);

Это одна из микрооптимизаций, правда не факт, что она вообще имеет смысл. Сказал бы, почему именно, но объяснение будет тянуть на целую статью по скриптингу (нужно долго вникать по поводу того, когда и какие инструкции jump/j** генерирует компилятор).
Пока что ограничусь таким советом: если нужна производительность, лучше избегайте цикла for, для него компилятор почти во всех случаях ставит лишние инструкции перехода.
Правда, всё равно от таких микрооптимизаций ощутимый прирост скорости будет заметен только в синтетических тестах с циклами на миллионы итераций.

$continue$
30.07.2016, 00:19
Лучше пожертвовать пару мс (машины на которых будет запускаться этот код - довольна таки быстрые), чем потерять "удобный" и простой синтаксис for.

P.S: Ты о этих самых переходах?

http://i.imgur.com/VTrTGs4.png
http://i.imgur.com/IOqAMu0.png
http://i.imgur.com/DoRVcI0.png


лучше избегайте цикла for

DeimoS
30.07.2016, 06:46
Лучше пожертвовать пару мс (машины на которых будет запускаться этот код - довольна таки быстрые), чем потерять "удобный" и простой синтаксис for.

Речь, как я понимаю, шла о ситуациях, когда микрооптимизация очень нужна и важна. В Pawn такие ситуации будут, когда кто-то вдруг решит написать очередной "сверхбыстрый *название_системы*!!!1111!!!". Ну и в часто вызываемых колллбэках можно её учесть.
Г - гибкость

Daniel_Cortez
31.07.2016, 15:26
Лучше пожертвовать пару мс (машины на которых будет запускаться этот код - довольна таки быстрые), чем потерять "удобный" и простой синтаксис for.
Шире нужно мыслить. Как уже сказал оратор выше, есть ситуации, в которых пригодится любая микрооптимизация. Например, я ими пользуюсь, когда пишу инклуды с перехватами функций - никогда не знаешь, как будут использовать перехваченные функции, поэтому лучше лишний раз перестраховаться, сделав так, чтобы перехватчик создавал как можно меньше дополнительной нагрузки.



P.S: Ты о этих самых переходах?

http://i.imgur.com/VTrTGs4.png
http://i.imgur.com/IOqAMu0.png
http://i.imgur.com/DoRVcI0.png

Почти, только вместо x86 набор инструкций AMX, а изменить направление цикла или трансформировать его в while/do-while можно только вручную из-за примитивности компилятора.