Все видели в SimCity и Cities:Skyline и подобных им, что в городе можно клацнут
по пешеходу / машине и вам высветится интерфейс, в котором есть: Имя персонажа, место жительства, место работы и куда он направляется.
Насколько я понимаю, эти жители, просто объекты одного, а может и нескольких классов
и создать такое же не тяжело. А когда города под 100к человек??.. Так вот вопрос к опытным программистам: мне кажется это очень "тяжелая" для ресурсов ноша. 100к + объектов? и нормальные системные требования? быть не может..
В чем подвох? Или лучше сказать моя недальновидность.
Ответы
Ответ 1
То, что в городе 100к объектов, не значит, что создаются объекты для них всех.
100к объектов, которые, скажем, весят по 200 байт - это 20 мегабайт. Вместится. Проблемы
правда, будут с GC, скорей всего. Но, думаю, можно настройки generations подкрутить так, чтоб не было больших фризов при сборке.
По существу же, сомнительно, что все 100к объектов всегда в памяти. Я как минимум несколько способов вижу оптимизации:
1) Разбиение на зоны/локации. Грузить объекты только в рамках этой зоны. Вспомнит
старые игры, при переходах между локациями было окно загрузки. Если мне память не изменяет, то в той же WoW'ке в своё время при переходах между локациями оно было. Это сейчас уже они оптимизировали, что не приходится ждать и создаётся иллюзия бесшовного мира.
2) Использовать пулы объектов. Чтоб не грузить лишний раз GC, да и просто, чтоб н
тратить ресурсы на создание объектов, используются пулы. Скажем, изначально создат
пул из 100 объектов-пустышек. По мере надобности заполнять их свойства нужными данным
(когда человек/здание/машина в зоне видимости). Когда объект выходит из зоны видимости, очищать объект и запихивать назад в пул. Потом этот объект из пула уже возьмёт другой персонаж игрового мира. Я такие пулы использовал для объектов, которые часто создаются: пули, заклинания и т.п.
Я могу привести код из текущего нашего проекта. Я там использую org.apache.commons.pool2 для создания пула подключений к базе данных:
Например, метод, который грузит мета данные для сервиса из базы:
public void loadMetaInfo(){
LOG.debug("load meta info");
DB db = null;
try{
db = mDB.getResource(); // получаем подключение из пула
String reqId = getRequestId();
db.send(new CommandRequest(Command.INFO.toString(), reqId), this);
mRequests.put(reqId, new CacheItem("load info"));
}
catch(Exception ex){
LOG.error("loadMetaInfo: db_problem");
}
finally{
if(db != null)
db.close(); // после окончания работы возвращаем объект в пул,
// чтоб им могли воспользоваться другие клиенты
}
}
3) Как заметили в комментариях, для оптимизация работы с памятью можно предотвращат
создание экземпляров классов, имеющих общую сущность, используя паттерн Приспособлене
(Flyweight pattern). Почитать про него (да и про многие другие паттерны) можно на этом сайте. По сути, это похоже на п.2. Типичный пример использования этого шаблона - Buffer pools.
Паттерн Приспособленец (Flyweight)
Это структурный шаблон проектирования, позволяющий использовать разделяемые объект
сразу в нескольких контекстах. Данный паттерн используется преимущественно для оптимизации работы с памятью.
Скажем, если мы хотим написать редактор текстовый. Для простоты положим, что используютс
только буквы русского алфавита. Создавать объект на каждую букву - накладно очень. Можн
определить словарь, состоящий из 33 объектов, которые мы будет переиспользовать. В итоге, к примеру, для текста из 330 букв мы уменьшили число создаваемых объектов в 10 раз. Если речь о книге, то там речь уже на тысячи идёт.
Ключевым моментом является разделение состояния на внутренне и внешнее. Внутренне
состояние от контекста не зависит. В примере с символами внутреннее состояние описывается кодом символа из таблицы кодировки. Так как внутреннее состояние не зависит от контекста, то оно может быть разделяемым и поэтому выносится в разделяемые объекты.
Внешнее состояние зависит от контекста и является изменчивым. В применении к символа
внешнее состояние может представлять положение символа на странице (строка + колонка). То есть код символа может быть использован многими символами, тогда как положение на странице будет для каждого символа индивидуально.
Применительно к вашему вопросу. У каждого объекта внутренним состоянием может задаватьс
размер объекта, его материал, какие-то физические величины, параметры коллайдера. Внешнее же состояние: имя персонажа, место жительства, место работы и куда он направляется.
Можно показать принцип работы UML диаграммой с Wiki:
Применительно к вашему вопросу накидаю по-быстрому примерную реализацию на Java
Проверить код сейчас нет возможности, но, как мне кажется, главное - уловить суть.
Скажем, у нас персонажи двух типов: толстые и худые. Тогда будет что-то такое.
Базовый класс, определяющий объект-персонаж:
public abstract class Character{
protected String mTitle;
protected String getTitle() {
return mTitle;
}
protected void setTitle(String mTitle) {
this.mTitle = mTitle;
}
protected int getWidth() {
return mWidth;
}
protected void setWidth(int mWidth) {
this.mWidth = mWidth;
}
protected int getHeight() {
return wHeight;
}
protected void setHeight(int wHeight) {
this.wHeight = wHeight;
}
protected int mWidth;
protected int wHeight;
public abstract void printCharacter();
}
Две его реализации:
public class FatCharacter extends Character {
public FatCharacter(){
setTitle("Я толстяк");
setWidth(20);
setHeight(40);
}
@Override
public void printCharacter() {
System.out.println("Title: " + getTitle() + ", width:" + getWidth() + "
height: " + getHeight());
}
}
public class ThinCharacter extends Character {
public ThinCharacter(){
setTitle("Я худой");
setWidth(10);
setHeight(40);
}
@Override
public void printCharacter() {
System.out.println("Title: " + getTitle() + ", width:" + getWidth() + "
height: " + getHeight());
}
}
Фабрика, собственно, которая отвечает за создание объектов:
public class FlyweightFactory {
public static enum Code{ Thin, Fat};
private Map mCharacters = new EnumMap(Code.class);
public Character getCharacter(Code characterCode){
Character character = mCharacters.get(characterCode);
// если такого объекта ещё нету, то создаём
if (character == null){
switch (characterCode){
case Fat :
character = new FatCharacter();
break;
case Thin :
character = new ThinCharacter();
break;
}
// добавляем в пул
mCharacters.put(characterCode, character);
}
return character;
}
}
Использовать так:
Character character = factory.getCharacter(Code.Fat);
character.printCharacter();
Комментариев нет:
Отправить комментарий