Рейтинг темы:
  • 0 Голос(ов) - 0 в среднем
  • 1
  • 2
  • 3
  • 4
  • 5
Система переменных игрока
#1
Здравствуйте, уважаемые.

Делюсь с вами своей системой сохранения короткой информации для игрока.

Бывают случаи, когда игроку нужно завести дополнительное поле информации, но заводить ради этого поле в таблице 'characters' или вовсе новую таблицу не удобно.
Для таких случаев можно воспользоваться этой системой.


Позволяет сохранить любую короткую информацию (255 символов строки или цифровое значение) в отдельную таблицу по OID игрока и названию-ключу, а так же прочитать её.
Загрузка "ленивая", только при создании; новые переменные создаются в мапе класса и в базе параллельно, ни каких дополнительных подгрузок не производится.
Класс потокобезопасен, использует ConcurrentHashMap.

Подключение класса производится только в L2PcInstance (для L2JServer и его форков), путём создания приватного поля и метода-геттера, а так же добавлением соответствующих строк в store() и restore(). Подробнее в архиве, в файле L2PcInstance.java.txt

Возможные проблемы:
Если в вашей БД поле 'obj_Id' таблицы 'characters' имеет тип, отличный от 'INT(10) UNSIGNED NOT NULL', или они имеют другие названия - то придется в классе подправить строку создания таблицы, либо создать таблицу 'character_variables' с полями 'oid' (как в characters с установкой внешнего ключа на него), 'key' (VARCHAR(50)), 'value' (VARCHAR(255)), 'time' (TIMESTAMP) самим.

Архив с классом и инструкциями по подключению: PlayerVariables.rar

Конструктивная критика, и предложения по дополнению или изменению дизайна класса приветствуются.
Критика вида "да такое есть в L2BlaBlaMegaSuperRulezServer сборке!" не принимаются. Систему подобную я использую очень давно, и конкретно сейчас писал для форка l2jserver.
Johnson получился в результате деления на null. Помогаю с джавой только за булочки с маком.
Ответ
#2
Предложения:
1. Добавить синхронизацию методу load в PlayerVariables, либо сделать loaded атомарной, а при входе в метод делать compareAndSet.
Почему: если из разных потоков вызвать load, то будет очень неприятная ситуация.

2. Не дергать на каждый чих базу данных, а кешировать. Сделать shutdown hook, который при выключении сервера будет все это дело сейвить; либо при выходе игрока из игры.
Почему: протестируйте на 1000 онлайна, при активном использовании данной штуки, будет очень сильное удивление, что сервер "логаит", а база данных вообще отваливается.
m0nster.art - clear client patches, linkz to utils & code.
Гадаю по капче.
Ответ
#3
В shutdownHook и так есть сохранение персонажей, оно за собой потянет и сохранение переменных, так что смысла отдельно сторить нет.

Про нагрузку, чесно говоря не подумал, не пришло в голову, что туда могут писать так часто.

А вот про синхронизацию забыл, хотя и не уверен в её необходимости. Пакеты в любом случае приходят последовательно, значит и загрузка из двух потоков одновременно не произойдет. Но на всякий случай метод синхронизировал.

Перезалил, спасибо большое за подсказку!
Johnson получился в результате деления на null. Помогаю с джавой только за булочки с маком.
Ответ
#4
При удалении чара корни остаются. Нужно подчищать, да. Smile
Родился, живу и когда-нибудь умру.
Ответ
#5
Johnson Написал:А вот про синхронизацию забыл, хотя и не уверен в её необходимости. Пакеты в любом случае приходят последовательно, значит и загрузка из двух потоков одновременно не произойдет. Но на всякий случай метод синхронизировал.

Перезалил, спасибо большое за подсказку!

Приходят то они последовательно, а выполняются асинхронно (: Во всяком случае на лыже. Отсюда идут различные интересные вещи, вроде двойного EnterWorld.
В любом случае атомарные операции много шустрее, чем обычный лок, поэтому рекомендую в данном случае заюзать именно их.
m0nster.art - clear client patches, linkz to utils & code.
Гадаю по капче.
Ответ
#6
Donatte;384387 Написал:При удалении чара корни остаются. Нужно подчищать, да.

Код:
CONSTRAINT `FK_VARS_ON_CHARACTER` FOREIGN KEY (`oid`) REFERENCES `characters` (`obj_Id`) ON UPDATE CASCADE ON DELETE CASCADE

Внешний ключ на каскадное удаление/изменение для того и нужен, чтобы корни подчищать.
Если на таблицу ведут внешние ключи - сначала обработаются их условия изменения.
Johnson получился в результате деления на null. Помогаю с джавой только за булочки с маком.
Ответ
#7
Не нужно усложнять.
Загружаем один раз в map при загрузке данных персонажа.
При любой изменении переменной, кроме того что пишем в map, сразу же сохраняем эту переменную в базу.
Так мы упрощаем алгоритм и перестраховываемся от потери данных при неожиданном падении или выключении сервера.

Вот подобное из исходников l2open.
PHP код:
<?php 
private final FastMap<String, String> user_variables = new FastMap<String, String>().setShared(true);

public
void setVar(String name, String value)
{
user_variables.put(name, value);
mysql.set("REPLACE INTO character_variables (obj_id, type, name, value, expire_time) VALUES (?,'user-var',?,?,-1)", _objectId, name, value);
}

public
void unsetVar(String name)
{
if(
name == null)
return;

if(
user_variables.remove(name) != null)
mysql.set("DELETE FROM `character_variables` WHERE `obj_id`=? AND `type`='user-var' AND `name`=? LIMIT 1", _objectId, name);
}

public
String getVar(String name)
{
return
user_variables.get(name);
}

public
boolean getVarB(String name, boolean defaultVal)
{
String var = user_variables.get(name);
if(var ==
null)
return
defaultVal;
return !(var.
equals("0") || var.equalsIgnoreCase("false"));
}

public
boolean getVarB(String name)
{
String var = user_variables.get(name);
return !(var ==
null || var.equals("0") || var.equalsIgnoreCase("false"));
}

public
FastMap<String, String> getVars()
{
return
user_variables;
}

private
void loadVariables()
{
ThreadConnection con = null;
FiltredPreparedStatement offline = null;
ResultSet rs = null;
try
{
con = L2DatabaseFactory.getInstance().getConnection();
offline = con.prepareStatement("SELECT * FROM character_variables WHERE obj_id = ?");
offline.setInt(1, _objectId);
rs = offline.executeQuery();
while(
rs.next())
{
String name = rs.getString("name");
String value = Strings.stripSlashes(rs.getString("value"));
user_variables.put(name, value);
}
}
catch(
Exception e)
{
e.printStackTrace();
}
finally
{
DatabaseUtils.closeDatabaseCSR(con, offline, rs);
}
}

Если нужно именно интенсивно изменять какие то данные то лучше это сделать по другому.
Ответ
#8
flopix Написал:Не нужно усложнять.
Загружаем один раз в map при загрузке данных персонажа.
При любой изменении переменной, кроме того что пишем в map, сразу же сохраняем эту переменную в базу.
Так мы упрощаем алгоритм и перестраховываемся от потери данных при неожиданном падении или выключении сервера.

Сервера ведь каждую минуту падают, особенно с нормальными администраторами... А потом берем тачки с двумя хеонами, овер9000 гб озу, строим БД кластеры для сервера с онлайном 1500.
Л2ж дев такой л2ж.

У меня есть знакомый один, который построил кластер БД на 6ти машинах, для сервера майнкрафт, что бы нормально держать онлайн 1000. Это очень мне напоминает, да.
m0nster.art - clear client patches, linkz to utils & code.
Гадаю по капче.
Ответ
#9
Ну все таки эта система была придумана для редко изменяющихся данных, таких как настройки, какие то флаги для сервисов и прочее. Откуда тут быть нагрузке на базу даже при большом онлайне?
Ведь никто не будет в такой системе хранить счетчик выбитой адены или убитых мобов?
Ответ
#10
Pointer*Rage;384391 Написал:майнкрафт, что бы нормально держать онлайн 1000
Весьма оптимистичный знакомый, да...

Перезалил с атомарниками по совету Pointer*Rage, спасибо большое за подсказки.

Если что-то важное туда писать - то можно и .store() вызвать для надежности.

Добавлено через 1 минуту
flopix Написал:Ну все таки эта система была придумана для редко изменяющихся данных, таких как настройки, какие то флаги для сервисов и прочее. Откуда тут быть нагрузке на базу даже при большом онлайне?
Ведь никто не будет в такой системе хранить счетчик выбитой адены или убитых мобов?

Хотя знаешь, ты не прав. Сейчас вспомнил один из форков l2r, там какую-то статистику писали в них. Премию Дарвина за это, конечно, давать надо. Но л2ж дев такой л2ж дев (с)
Johnson получился в результате деления на null. Помогаю с джавой только за булочки с маком.
Ответ


Возможно похожие темы ...
Тема Автор Ответы Просмотры Последний пост
  Не пропадает опыт после смерти игрока Magican 1 1,256 04-07-2020, 06:41 PM
Последний пост: Magican
  заставить игрока подобрать айтем slayer48 9 2,629 08-05-2015, 02:13 PM
Последний пост: Gawric
  Система достижений Map 2 1,555 03-06-2015, 02:05 PM
Последний пост: ProDev
  Система достижений в Альт+Б itcry 7 2,421 02-14-2015, 05:41 PM
Последний пост: Donatte
  [ШАРА]Система рейтинга GOD для HF (частичные сурсы/компил) OneThunder 0 1,428 08-05-2014, 11:34 AM
Последний пост: OneThunder
  Смерть игрока Map 7 2,192 06-30-2014, 03:41 PM
Последний пост: Map
  Система аванпостов L2J 0 1,083 04-16-2014, 03:24 PM
Последний пост: L2J
  Система Атрибутов Auri 15 2,975 01-28-2014, 05:50 PM
Последний пост: Auri
  Система тиккетов (Support) Frozenn 8 2,615 03-14-2013, 07:49 PM
Последний пост: KilRoy
  удалить предмет у игрока mego4el 0 1,147 08-25-2012, 11:42 PM
Последний пост: mego4el

Перейти к форуму:


Пользователи, просматривающие эту тему: 1 Гость(ей)