PDA

Просмотр полной версии : [Вопрос] Помогите исправить алгоритм удаления дома (всегда удаляется последний дом)



PawnoNoob
09.06.2018, 00:30
Приветствую. Столкнулся с проблемой при создании одной из систем. В общем, есть система - удаление домов через диалоговое окно с использованием цикла. Для наглядности приложу код:

есть enum с переменными:

#define MAX_HOUSES (25) // для начала сделал именно 25, чтобы потом увеличивать;
enum hinfo
{
house_ID,
Float:house_X,
Float:house_Y,
Float:house_Z
}
new hInfo[MAX_HOUSES][hinfo], TOTALHOUSES;

естественно, есть и загрузка:
public LoadHouses()
{
new rows;
cache_get_row_count(rows);
if(rows)
{
for(new i = 1; i <= rows; i++)
{
TOTALHOUSES++;
cache_get_value_name_int(i-1, !"id", hInfo[i][house_ID]);
cache_get_value_name_float(i-1, !"x", hInfo[i][house_X]);
cache_get_value_name_float(i-1, !"y", hInfo[i][house_Y]);
cache_get_value_name_float(i-1, !"z", hInfo[i][house_Z]);

// здесь планировал создание пикапов и тому подобных вещей, но не об этом речь;
}
}
return true;
}

есть команда для удаления:
cmd:ahouse(playerid)
{
return ShowPlayerDialog(playerid, 0, DIALOG_STYLE_LIST, !"ahouse", !"1. Delete house", !"Ok", !"No");
}

и, соответственно, код в самом диалоге:
...
case 0:
{
for(new i = 1; i <= TOTALHOUSES; i++)
{
// здесь показывает список домов от 1 до последнего (из TOTALHOUSES);
}
}

Проблема в том, что при удалении, например, первого дома, его id остаётся в списке, но пропадает последний. В базе, соответственно, тоже удаляется (пикапы, тексты и тому подобные вещи удаляются тоже).

Сомневаюсь в решении, но нужно ли использовать в этой ситуации итераторы? Если да, то каким образом это реализовать можно? :sorry:
Заранее благодарен за ответ! :blush2:

pawnoholic
09.06.2018, 01:19
Сомневаюсь в решении, но нужно ли использовать в этой ситуации итераторы? Если да, то каким образом это реализовать можно?

Примерно, как то так:

new Iterator:House<MAX_HOUSES>;

public LoadHouses()
{
new
idx,
rows = cache_num_rows();

for (new i; i < rows; i++)
{
if ((idx = Iter_Free(House)) == INVALID_ITERATOR_SLOT) {
break;
}

cache_get_value_name_int(i, "id", hInfo[idx][house_ID]);
// ...

Iter_Add(House, idx);
}
}

PawnoNoob
09.06.2018, 01:24
Примерно, как то так:

new Iterator:House<MAX_HOUSES>;

public LoadHouses()
{
new
idx,
rows = cache_num_rows();

for (new i; i < rows; i++)
{
if ((idx = Iter_Free(House)) == INVALID_ITERATOR_SLOT) {
break;
}

cache_get_value_name_int(i, "id", hInfo[idx][house_ID]);
// ...

Iter_Add(House, idx);
}
}

А можете дать ссылку на тему, где есть урок с использованием итераторов? :sad:

И ещё. Можете сказать, как удалить этот итератор? Ну, при удалении дома, например

pawnoholic
09.06.2018, 01:30
А можете дать ссылку на тему, где есть урок с использованием итераторов?

http://forum.sa-mp.com/showthread.php?t=571159


И ещё. Можете сказать, как удалить этот итератор? Ну, при удалении дома, например

Iter_Remove(House, id);

PawnoNoob
09.06.2018, 01:32
http://forum.sa-mp.com/showthread.php?t=571159



Iter_Remove(House, id);

А как их выводить? Ну, список домов в диалоговое окно :pardon:

StevenH
09.06.2018, 01:50
А как их выводить? Ну, список домов в диалоговое окно :pardon:

С помощью foreach, например:



foreach(new h: House)
{
// какой то код
}


На самом деле итераторы довольно полезная и удобная штука)

DeimoS
09.06.2018, 16:26
Примерно, как то так:

new Iterator:House<MAX_HOUSES>;

public LoadHouses()
{
new
idx,
rows = cache_num_rows();

for (new i; i < rows; i++)
{
if ((idx = Iter_Free(House)) == ITER_NONE) {
break;
}

cache_get_value_name_int(i, "id", hInfo[idx][house_ID]);
// ...

Iter_Add(House, idx);
}
}

Не совсем понятно зачем тут Iter_Free. У тебя ведь итератор имеет тот же размер, что и массив с домами, а значит логично просто проверить количество возвращённых строк, дабы получить тот же эффект, но без постоянного насилования Iter_Free (который, к слову, содержит в себе цикл). Да и не совсем понятен смысл поля "id", если у тебя есть итератор, который отлично сортирует данные.

И не стоит использовать cache_num_rows, если ты записываешь значение в массив :) Этим ты вдвойне насилуешь стэк, ибо cache_num_rows является обычным stock, в котором создаётся переменная и вызывается cache_get_row_count.


Я бы сделал так:
Поле id оставил бы в виде "AUTO_INCREMENT" и не трогал бы его. Пусть этот столбец будет нужен исключительно MySQL. А для ID домов на сервере я создал бы ещё один столбец (например, "house_id") и контролировал бы его уже сам.
Зачем это нужно? Во-первых, это позволит быть уверенным в том, что твой запрос на выгрузку данных вернёт ровно столько строк, сколько есть ячеек (если ты, конечно, не уменьшишь число ячеек в один момент, указав меньшее число, чем будет домов на сервере). Во-вторых, это позволит использовать этот ID в качестве индекса массива, тем самым ты сможешь обращаться к базе с домами, не обращаясь, при этом, к массиву, дабы получить ID дома, так как он будет равен индексу. Возможно, не очень понятно, так что покажу пример загрузки.


new Iterator:House<MAX_HOUSES>;

public LoadHouses()
{
new row_count;
cache_get_row_count(row_count);

if(row_count > MAX_HOUSES)// Проверка нужна как раз на случай, если ты решишь изменить значение MAX_HOUSES и укажешь его меньше, чем есть домов в базе
{
row_count = MAX_HOUSES;
print("Warning: Тут сообщение с предупреждением о том, что часть домов не загрузилось");
}

for (new i, idx; i < rows; i++)
{
cache_get_value_name_int(i, "house_id", idx);// Выгружаем ID дома

cache_get_value_name_float(i, "x", hInfo[idx][house_Y]);// И тут уже используем значение "idx" в качестве индекса
cache_get_value_name_float(i, "y", hInfo[idx][house_Y]);
cache_get_value_name_float(i, "z", hInfo[idx][house_Z]);

// ...

Iter_Add(House, idx);// + заносим индекс в итератор
}

printf("Загружено %d домов", row_count);
return 1;
}

А при создании дома уже используем Iter_Free, тем самым сразу проверяя лимит домов и определяя свободный ID для базы
new id = Iter_Free(House);
if(id == ITER_NONE)
{
SendClientMessage(playerid, -1, "Лимит домов исчерпан")
return 1;
}

format(...., "INSERT INTO house(house_id,...)VALUE(%d,....)", id);// Указываем значение Iter_Free в качестве значения "house_id"
// ...
Iter_Add(House, id);

PawnoNoob
10.06.2018, 10:11
Не совсем понятно зачем тут Iter_Free. У тебя ведь итератор имеет тот же размер, что и массив с домами, а значит логично просто проверить количество возвращённых строк, дабы получить тот же эффект, но без постоянного насилования Iter_Free (который, к слову, содержит в себе цикл). Да и не совсем понятен смысл поля "id", если у тебя есть итератор, который отлично сортирует данные.

И не стоит использовать cache_num_rows, если ты записываешь значение в массив :) Этим ты вдвойне насилуешь стэк, ибо cache_num_rows является обычным stock, в котором создаётся переменная и вызывается cache_get_row_count.


Я бы сделал так:
Поле id оставил бы в виде "AUTO_INCREMENT" и не трогал бы его. Пусть этот столбец будет нужен исключительно MySQL. А для ID домов на сервере я создал бы ещё один столбец (например, "house_id") и контролировал бы его уже сам.
Зачем это нужно? Во-первых, это позволит быть уверенным в том, что твой запрос на выгрузку данных вернёт ровно столько строк, сколько есть ячеек (если ты, конечно, не уменьшишь число ячеек в один момент, указав меньшее число, чем будет домов на сервере). Во-вторых, это позволит использовать этот ID в качестве индекса массива, тем самым ты сможешь обращаться к базе с домами, не обращаясь, при этом, к массиву, дабы получить ID дома, так как он будет равен индексу. Возможно, не очень понятно, так что покажу пример загрузки.


new Iterator:House<MAX_HOUSES>;

public LoadHouses()
{
new row_count;
cache_get_row_count(row_count);

if(row_count > MAX_HOUSES)// Проверка нужна как раз на случай, если ты решишь изменить значение MAX_HOUSES и укажешь его меньше, чем есть домов в базе
{
row_count = MAX_HOUSES;
print("Warning: Тут сообщение с предупреждением о том, что часть домов не загрузилось");
}

for (new i, idx; i < rows; i++)
{
cache_get_value_name_int(i, "house_id", idx);// Выгружаем ID дома

cache_get_value_name_float(i, "x", hInfo[idx][house_Y]);// И тут уже используем значение "idx" в качестве индекса
cache_get_value_name_float(i, "y", hInfo[idx][house_Y]);
cache_get_value_name_float(i, "z", hInfo[idx][house_Z]);

// ...

Iter_Add(House, idx);// + заносим индекс в итератор
}

printf("Загружено %d домов", row_count);
return 1;
}

А при создании дома уже используем Iter_Free, тем самым сразу проверяя лимит домов и определяя свободный ID для базы
new id = Iter_Free(House);
if(id == INVALID_ITERATOR_SLOT)
{
SendClientMessage(playerid, -1, "Лимит домов исчерпан")
return 1;
}

format(...., "INSERT INTO house(house_id,...)VALUE(%d,....)", id);// Указываем значение Iter_Free в качестве значения "house_id"
// ...
Iter_Add(House, id);

И как потом, допустим, по номеру (id) дома посмотреть его информацию? :blush2: Или присвоить значение pHouse игроку (которое равно id дома)

DeimoS
11.06.2018, 10:01
И как потом, допустим, по номеру (id) дома посмотреть его информацию? :blush2:

Эмм, так во всех случаях, когда ты работаешь с домами на сервере, у тебя есть индекс этого дома в массиве hInfo. Вот точно так же, как ты получаешь индекс для массива, получай ID дома - теперь это одно и то же.

Только теперь не забывай добавлять "LIMIT 1" ко всем запросам, где нужно изменить/получить лишь какую-то одну строку, дабы MySQL не продолжал обрабатывать все строки после нахождения нужной. В случае с выборкой по "AUTO_INCREMENT" MySQL делает это автоматически, ибо "A_I" поля не могут повторять значения и логично, что если одна строка найдена, то искать дальше нет смысла. А вот в случае с выборкой для полей без "AUTO_INCREMENT" лучше указывать "LIMIT" явно.


Или присвоить значение pHouse игроку (которое равно id дома)

При авторизации делай запрос в базу с домами и ищи ID аккаунта игрока среди ID владельцев (надеюсь, ты не хранишь в базе с домами ники, а хранишь ID аккаунтов).

StevenH
12.06.2018, 01:13
Дополню.. довольно правильное решение при авторизации аккаунта делать запрос в базу с домами и таким образом проверять владельца и записать в переменную, ту же pHouse (но без сохранения, и соответственно с обнулением при коннекте)..

Почему же?

Может быть довольно много проблем, например не раз замечал, что дом у игрока якобы есть, а в таблице с домами его нет (или наоборот). Конечно, тут косяк самой реализации, но лучше сделать удобнее и потом не мучаться!)

ziggi
12.06.2018, 03:51
В случае с выборкой по "AUTO_INCREMENT" MySQL делает это автоматически, ибо "A_I" поля не могут повторять значения и логично, что если одна строка найдена, то искать дальше нет смысла. А вот в случае с выборкой для полей без "AUTO_INCREMENT" лучше указывать "LIMIT" явно.

Скорее это относится не к AUTO_INCREMENT, а к Primary Key и Unique полям.

DeimoS
12.06.2018, 10:29
Дополню.. довольно правильное решение при авторизации аккаунта делать запрос в базу с домами и таким образом проверять владельца и записать в переменную, ту же pHouse (но без сохранения, и соответственно с обнулением при коннекте)..

Почему же?

Может быть довольно много проблем, например не раз замечал, что дом у игрока якобы есть, а в таблице с домами его нет (или наоборот). Конечно, тут косяк самой реализации, но лучше сделать удобнее и потом не мучаться!)

Вообще дублировать какую-либо информацию в MySQL стоит минимально. Это банально избавит от нужды контролировать актуальность этой самой информации.
А получить ID дома при загрузке можно не двумя отдельными запросами, а поместив запрос в запрос, а-ля:

SELECT a.*,IFNULL((SELECT h.id FROM house AS h WHERE a.id = h.owner_id LIMIT 1), -1) AS owned_house FROM account AS a WHERE player_name = 'Тут-Ник' LIMIT 1
В итоге просто выгружаешь аккаунт так же, как и выгружал, только помимо этого ещё обрабатываешь "owned_house"

cache_get_value_name_int(0, "owned_house", /*Переменная с ID дома*/);


Скорее это относится не к AUTO_INCREMENT, а к Primary Key и Unique полям.

Не думаю, что автор изучал документацию на эту тему, поэтому максимально постарался упростить :) phpMyAdmin обычно сразу устанавливает для поля параметр "Primary Key", когда выбиваешь "AUTO_INCREMENT"

ziggi
12.06.2018, 17:31
Не думаю, что автор изучал документацию на эту тему, поэтому максимально постарался упростить :) phpMyAdmin обычно сразу устанавливает для поля параметр "Primary Key", когда выбиваешь "AUTO_INCREMENT"

Да, AUTO_INCREMENT может иметь только Primary Key, просто есть ещё UNIQUE поля, в которых тоже не обязательно ограничивать размер выборки.

DeimoS
12.06.2018, 18:41
Да, AUTO_INCREMENT может иметь только Primary Key, просто есть ещё UNIQUE поля, в которых тоже не обязательно ограничивать размер выборки.

Как я уже писал ранее, детали были специально упущены. Я описывал лишь конкретную ситуацию, которая имеет отношение к вопросу автора, дабы он понимал ровно столько, сколько нужно для его ситуации. Вдаваться в подробности и рассказывать про индексы смысла особого нет, ибо без хотя бы минимального базиса эта информация ничего не даст автору. А с учётом того, что в SA-MP вообще редко кто осознанно пользуется возможностями MySQL, эта информация может стать просто не востребованной.

PawnoNoob
17.06.2018, 18:18
Чему равно значение INVALID_ITERATOR_SLOT?
У меня, видимо, очень старая версия foreach, поэтому выдаёт варнинги. :blush2:

DeimoS
17.06.2018, 20:12
Это макрос из y_foreach, а у тебя, видимо, обычный foreach :) Замени INVALID_ITERATOR_SLOT на ITER_NONE
P.S. Обновил свой код.

PawnoNoob
17.06.2018, 20:23
А в чем отличие между y_foreach и foreach?

DeimoS
17.06.2018, 20:25
y_foreach - часть библиотеки YSI (имеет зависимости от других инклудов библиотеки)
foreach - аналог y_foreach, только без зависимостей (то бишь, реализован как отдельный инклуд)

Точнее, сейчас уже не аналог, но отличия у них не существенные.

PawnoNoob
17.06.2018, 20:30
Получается так, что лучше использовать y_foreach вместо foreach, верно?
Если да, то как его подключать в мод?
(В мобильной версии сайта не вижу BB-кодов, поэтому так)
#include <YSI/YSI_Data/y_foreach ?
(Слеш в другую сторону только).

DeimoS
17.06.2018, 20:35
Получается так, что лучше использовать y_foreach вместо foreach, верно?

Если в моде используешь YSI, то логичнее пользоваться и y_foreach. Правда не уверен, что так просто всё заработает, ибо синтаксис у функций может отличаться.



Если да, то как его подключать в мод?
(В мобильной версии сайта не вижу BB-кодов, поэтому так)
#include <YSI/YSI_Data/y_foreach ?
(Слеш в другую сторону только).

Ну да. Хотя стоит проверить, может он подключается автоматически (просто попробуй вызвать одну из функций, которые находятся внутри инклуда)

pawnoholic
17.06.2018, 21:50
Получается так, что лучше использовать y_foreach вместо foreach, верно?
Если да, то как его подключать в мод?
(В мобильной версии сайта не вижу BB-кодов, поэтому так)
#include <YSI/YSI_Data/y_foreach ?
(Слеш в другую сторону только).

#include <YSI\y_iterate>

PawnoNoob
18.06.2018, 00:34
Если в моде используешь YSI, то логичнее пользоваться и y_foreach. Правда не уверен, что так просто всё заработает, ибо синтаксис у функций может отличаться.




Ну да. Хотя стоит проверить, может он подключается автоматически (просто попробуй вызвать одну из функций, которые находятся внутри инклуда)

Как я понял по ошибкам при объявлении переменной, автоматически он не вызывается, но когда я подключаю его в мод, то вижу вот это:
D:\****\include\YSI\YSI_Data\..\YSI_Internal\..\YSI_Core\..\YSI_Storage\..\YSI_Internal\y_thirdpartyinclude.inc(138) : fatal error 111: user error: Please update "https://github.com/Zeex/amx_assembly" to get "deref()"

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


#include <YSI\y_iterate>

"Невозможно прочитать...." (или как там):sad:

pawnoholic
18.06.2018, 01:22
Please update "https://github.com/Zeex/amx_assembly" to get "deref()"

скачай amx_assembly и закинь его в директорию include

PawnoNoob
18.06.2018, 03:54
Столкнулся ещё с одной проблемой, связанной с выбором определённого дома в диалоговом окне.

Вывожу примерно так:
foreach(new h: House)
{
// здесь информация о каждом доме (класс, номер, стоимость);
}
return ShowPlayerDialog(playerid, 0, DIALOG_STYLE_LIST, ....

При выборе определённого пункта через формат вывожу его номер.
В чём заключается проблема: допустим, если я удалил дом с номером 9 из базы, то в диалоговом окне, при выборе 10 пункта, будет выводить 0 номер дома, а при выборе 11 - 10. И так далее. Нарушается порядок :pardon:
new string[3];
format(string, sizeof(string), "%i", HouseInfo[listitem][hNumber);
return SendClientMessage(playerid, -1, string);