Добавлен , опубликован
Когда нет интернета и есть лишь юнити, я немного балуюсь в юнити. Решил вам немного подробнее рассказать о проекте под названием Project S.
Project S - это некий проект в котором я пробую различные решения в юнити. В прошлый раз я сделал леталку в космосе. На этот раз я сделал систему расчета потоков. Сильно подробно рассказывать не буду, лишь покажу и объясню парочку скриншотов, а так же расскажу о своем архитектурно решении.
И так поехали.

Примерчики

Первая система которую мы рассмотрим будет элементарной. Она состоит из 3 объектов, соединенных между собой.
  • EnergyProduction - генератор энергии
  • EnergyStore - типичный аккумулятор
  • EnergyConsumption - потребитель
Над каждым из объектов есть небольшое описание в формате:
Energy: 0.00 / 50.00 [0.00] 1.00
// 0.00 / 50.00 - первое число, текущее значение данного ресурса в объекте. Второе - максимальная вместимость.
// [0.00] - производительность, сколько ед. ресурса объект вырабатывает (ну или тратит) за единицу времени.
// 1.0 - эффективность объекта, вычисляется на основе внутренних данных.
В данной системе сеть устроена таким образом:
Генератор ( +3) соединен с Аккумулятором (15), который в свою очередь соединен с Потребителем (-5).
Изначально аккумулятор наш разряжен, но со временем он потихоньку заряжается. Но тут возникает сразу же проблема, к нашему аккумулятору подключен потребитель, который поглощает практически всю энергию с аккумулятора.
Давайте теперь рассмотрим эффективности каждого из этих элементов:
  • Генератор - работает на все 100%, отдает всю энергию на потребности аккумулятора
  • Аккумулятор - он всегда работает на все 100%
  • Потребитель - в данном случае его эффективность равно 60%. Потому что для полной мощности ему требуется 5 ед. энергии, а ему выделяется только 3 ед. от аккумулятора.
Теперь рассмотрим схему поинтереснее. Суть данной системы в том, что мы добавили еще один Генератор ( +2) и подключили его к нашему потребителю. И как видно, теперь он работает на все 100%, т.к. новый генератор восполняет недостаток энергии в 2 ед.
А теперь прокачаем наш новый генератор, чтобы он производил 5 ед. энергии и понаблюдаем за происходящим.
Как видно из серии скриншотов, наш аккумулятор теперь заряжается полностью, т.к. он не расходует свою энергию на потребителя, потому что потребитель берет энергию из второго генератора, которой хватает чтобы обеспечить максимальную эффективность объекта. Так же можно заметить, что основной генератор (который заряжает наш аккумулятор) со временем теряем эффективность, т.к. с каждым разом аккумулятору требуется все меньше и меньше энергии для зарядки. После полного заряда эффективность генератора станет равна 0. Вот такие вот пироги в данной схеме.
Теперь поэкспериментируем вот с такой вот схемой. На самом деле она простая, просто не хочу удалять нужные потом объекты, поэтому отключим наш первый потребитель от аккумулятора и добавим нового Потребителя (-5), подключенного ко второму генератору. Теперь рассмотрим эффективности наших потребителей. Как видно они оба работают на 50%. Это потому что система стремится ублажить все подключенные объекты и распределяет ресурс равномерно.
Как видите, при разных конфигурациях системы ресурсы распределяются равномерно, обеспечивая равную эффективность производительности. Но на третий скриншот стоит обратить особое внимание, здесь ситуация немного иная и такое распределение ресурсов обусловлено еще тем, что пропускная способность каналов равна 10. Поэтому на второго потребителя максимум может пойти 10 ед. ресурса, и поэтому его эффективность занижена, но если рассчитать эту эффективность с этим ограничение, мы увидим что система все таки равномерно распределила ресурсы в сети.
0.22 * 15.0 / 10.0 = 0.33

Реализация

После того как мы рассмотрели несколько интересных ситуаций, я смогу рассказать о том как это все реализовано. Конкретно какой то популярный алгоритм по расчету транспортной сети я не использовал, решение получилось численным, но крайне эффективным и интересным. Конкретной реализации тут вы не увидите, потому что я покажу как я решил данную задачу архитектурно в Unity3D.
В общем все держится на 4 классах и 1 интерфейсе.
  • IResourceManager - содержит основные методы, необходимы для расчета потока ресурсов в сети.
  • ResourceManager - главный контроллер, которые запускает расчет потока.
  • ResourceController - отвечает за поведение какого-либо ресурса в сети. Но на один объект нельзя вешать больше одного типа ресурса с разными действиями (возможно потом я это доработаю).
  • ResourceElementController - отвечает за расчет ресурсов в конкретном объекте, собирает информацию о всех ресурсах в данном объекте и т.д. Реализует IResourceManager
  • ResourceTubeController - труба. Думаю тут все и так понятно. Реализует IResourceManager
Теперь думаю можно поговорить о IResourceManager и какие тайны он в себе хранит:
bool IsUseResource(...)
// группа методов с разным поведением и одинаковым результатом. Отвечает на вопрос, используется ли вообще какой либо ресурс в объекте. Ну например можно узнать, производит этот элемент хоть какой-нибудь ресурс или есть ли у него вообще хоть какой-нибудь тип использования для ресурса **Энергия** и т.д.

float InputResource(ResourceType resourceType, float inputValue)
// метод, которые передает определенное кол-во ресурсов и возвращает сколько ресурсов взяли.

float OutputResource(ResourceType resourceType, float needValue)
// аналогичный методу InputResource, только возвращает число равному кол-ву ресурсов, которые нам смогли выделить

void BeginComputing()
// вызывается для инициализации цикла вычисления. Нужен для подчистки временных данных

void ComputeResource(ResourceType resourceType)
// распределение заданного ресурса в текущем узле
Ну а теперь пришло время знакомиться с ResourceManager'ом. Данная штука элементарна. Содержит в себе приоритетную очередь без прерываний. Приоритет у TubeController выше, чем у ResourceElementController. А теперь алгоритм по шагам.
  1. Сперва сбрасываем все узлы, вызывая BeginComputing()
  2. Затем выбираем конкретный слой сети (Каждый ресурс - отдельный слой транспортной сети)
  3. Инициализируем очереди
    • Добавляем в очередь все работающие генераторы для заданного ресурса
    • Добавляем в очередь все работающие аккумуляторы для заданного ресурса
  4. Запускаем цикл до тех пор, пока очередь не опустеет
    • Выбираем первый элемент из очереди
    • Проверяем, что мы еще не вычисляли для этого узла данные, если запускали вычисление, то пропускаем его
    • Запускаем ComputeResource(...). Внутри этого элемента идет добавление новых элементов в очередь.
Все это дело запускается в FixedUpdate.
Ну как то так. Теперь можно рассмотреть как происходит все это дело для самой первой схемы:
тыкай сюда
  • Шаг 1.
После инициализации в очереди оказались Генератор и Аккумулятор.
  • Шаг 2.
После расчета Генератора, в очередь втискивается соединитель Аккумулятора и Генератора (Труба1) и очередь становиться такой: Труба1, Аккумулятор.
  • Шаг 3.
Труба1 отдает полученные ресурсы в Аккумулятор
Очередь: Аккумулятор, Аккумулятор
  • Шаг 4.
Аккумулятор начинает передавать ресурс в соединитель Аккумулятора и Потребителя (Треба2)
Очередь: Труба2, Аккумулятор
  • Шаг 5.
Труба2 отдает полученные ресурсы от Аккумулятора в Потребитель
Очередь: Аккумулятор, Потребитель
  • Шаг 6.
Аккумулятор уже считали, поэтому идем дальше.
Очередь: Потребитель
  • Шаг 7.
Потребитель ничего не делает.
Очередь: <empty>
Расчет окончен! Так же как и эта мини-записка

Project S #1 - прошлое баловство
Транспортная сеть - стать о транспортных сетях на википедии
Очередь с приоритетом - статья про очереди с приоритетом на википедии
`
ОЖИДАНИЕ РЕКЛАМЫ...