Страницы

Поиск по вопросам

вторник, 26 ноября 2019 г.

SimsCity 4 и Cities: SkyLine. Население


Все видели в 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();

Комментариев нет:

Отправить комментарий