ПрограммированиеФорумОбщее

Разумна ли такая архитектура?

Страницы: 1 2 3 4 5 6 7 Следующая »
#0
0:31, 2 янв 2011

#Warning: Впереди стена текста!

Разумна ли такая архитектура?
Здравствуйте коллеги геймдевы. Продумываю архитектуру для нового проекта, начну с того, что это гибрид RPG/RTS с редактором в котором предусматривается множество способов расширять/изменять игровой мир (Вообще упор делается на этот редактор, вроде TESCS или Warcraft3 map editor). Основные языки С++ и lua (luabind) + множество всяческих полезных API, итак, приступаю к вопросам:
Есть ли смысл создавать динамическую компонентную систему? Т.е. делать что-то вроде

actor.AddComponent(ComponentFactory(“SomeFile.xml”));

Скорее всего, при этом придётся использовать систему событий для того, что бы общались между собой компоненты, не уверен, что это хорошая идея.
Можно сделать по-другому…

specificActor.AddPhysicsComponent(PhysicsComponentFactory(“SomeFile0.xml”, args…));
specificActor.AddAiComponent(AiComponentFactory(“SomeFile1.xml”, args…));

Тогда вполне можно обойтись без системы событий и расширять компоненты через lua.
Если же не делать динамические компоненты, то проблема, которую я хочу избежать (а именно эта проблема была в моём прошлом проекте), слишком сильная связность компонент, с другой стороны, реализовать такую систему компонент очень просто, и синтаксис её использования красив:

Actor<SpecialAiComponent, SpecialPhysicsComponent> actor;

Но, пожалуй, основная проблема, которую я не могу решить, и которая обсуждается, в том числе на этом форуме, как передавать данные вместе с событиями всем компонентам? Допустим, GraphicsComponent требует данные от HealthComponent. Критерия всего два, данные хотелось бы передавать через LUA и через С++, при этом максимально уменьшить связность компонент. Здесь так же куча способов, у всех способов которые я знаю, к сожалению, есть минусы :(
Второй вопрос, как Вы связываете графическую подсистему и игровые объекты? В прошлом проекте я использовал что-то типа singleton<Graphics>, проблем от этого было много, в том числе сильно падала скорость компиляции, так как почти все зависело от Graphics (Изменишь Graphics и жди потом 10 минут). Можно конечно передавать всего лишь какой-нибудь SceneGraphComponent который позволит загрузить mesh и его текстуры, а так же его передвигать, затем посылать события в Graphics если нужно сделать что-то  “Особенное”, опять же, не уверен, что в этом есть смысл… Кстати, у Вас объекты сами грузят свой mesh и текстуры из конструктора или какой-нибудь Init функции или Вы их передаете объекту?
Я так же сомневаюсь в том, что хорошо продумал систему событий. В данный момент я передаю объектам eventManager, который позволяет передавать идентификатор события всем подсистемам, подключенным к этому eventManager, например:

eventManager.Send(ACTOR_RCV_DAMAGE, actorId) // Посылает события указывающее на то, что actor #actorId получил урон

пошлет событие всем подсистемам, которые хотят получать событие с ключом ACTOR_RCV_DAMAGE, подсистема звука произведёт звук, подсистема шейдеров закрасит экран красным цветом и.т.д. Опять же, здесь возникает проблема в том, как передавать дополнительные данные о событии (Сейчас использую boost::any). Мне кажется, что такая система событий берёт на себя слишком много обязанностей, к примеру, практический пропал смысл в использовании конечных автоматов (Кроме AI и ещё пары мелочей), плохо это или хорошо, сам не пойму.
И наконец, очень хочется узнать, кто и как соединяет класс игровой логики, графики, ввода, GUI и прочего. У меня все классы существует отдельно, но в конце концов объекты этих классов содержатся в классе GameLogic, т.е. Подсистема графики ничего не знает о системе ввода, звука или GUI (лишь несколько callback’ов соединяет их), что хорошо, расстраивает лишь то, что GameLogic знает о всех ключевых классах, боюсь, это может привести к фейлу эпических масштабов, с другой стороны, как разделить игровую логику и остальные ключевые классы – я не знаю.
Буду благодарен, получить от Вас любые советы, наставления и конструктивную критику!

#1
4:22, 2 янв 2011

Stultus
> eventManager.Send(ACTOR_RCV_DAMAGE, actorId)
если тока через евенты то не возникнут ситуации что иногда надо обработать евет с учетом очереди, а иногда сразу?

если на сообщение висит несколько обработчиков и первый обработчик удалил инициализатор сообщения? как это будет работать при много поточности? Возникает много вопросов которые решаться но что то обязательно упустишь.

Stultus
> Опять же, здесь возникает проблема в том, как передавать дополнительные данные
> о событии
я для гуи как раз сейчас делаю чтобы работала вот такая запись.

void fTest(long l, float f, int i);

  CSignal<int,float,long> sgn;
  sgn.connect( fTest, arg2, 4.0 , arg0 );
  sgn.connect( fTest, 2, arg1 , arg0 );
        sgn.invoke(1,2.0,3);

Stultus
> Допустим, GraphicsComponent требует данные от HealthComponent. Критерия всего
> два, данные хотелось бы передавать через LUA и через С++, при этом максимально
> уменьшить связность компонент.
Stultus
> У меня все классы существует отдельно, но в конце концов объекты этих классов
> содержатся в классе GameLogic, т.е
Сколько не приходилось делать всегда прихожу к одному тупому варианту.

class BaseGraphicsComponent { Conteyner* pSuper; }
class BaseHealthComponent { Conteyner* pSuper; }
class Conteyner
{
public:
 BaseGraphicsComponent * pGC;
 BaseHealthComponent* pHC;
};

Можно даже без виртуальных функций обойтись. GraphicsComponent  которому нужны данные от HealthComponent спокойно может static_cast<HealthComponent *>(pHC) ведь он не может существовать без HealthComponent.
С GameLogic тоже самое заменить тока Component - gui,io,...

#2
17:05, 2 янв 2011

susageP
> если тока через евенты то не возникнут ситуации что иногда надо обработать евет
> с учетом очереди, а иногда сразу?
Добавил функцию triggerevent что бы сразу обрабатывалось без очереди.

> если на сообщение висит несколько обработчиков и первый обработчик удалил
> инициализатор сообщения? как это будет работать при много поточности? Возникает
> много вопросов которые решаться но что то обязательно упустишь.
Все сообщения передаю через самодельный shared_ptr предназначенный только для евентов, то есть таких проблем у меня не возникнет наверное. К тому же я использую две очереди, если евент сгенерировал ещё один евент, он попадает во вторую очередь и потом обрабатывается.

susageP
> я для гуи как раз сейчас делаю чтобы работала вот такая запись.
Выглядит как самодельный std::bind/std::function (tr1) или boost::bind.
Все же для событий это не подойдет, ведь единственный компонент о котором знает мой actor это eventmanager, а не конкретный слот для евента...

susageP
> Сколько не приходилось делать всегда прихожу к одному тупому варианту.
Похоже только так и получится :(
Проблема лишь в том, что расширять такой код через редактор довольно трудно.

Хотелось бы услышать ещё советов/мнений..

#3
20:10, 2 янв 2011

Stultus
> Выглядит как самодельный std::bind/std::function (tr1) или boost::bind.
> Все же для событий это не подойдет, ведь единственный компонент о котором знает
> мой actor это eventmanager, а не конкретный слот для евента...
+ boost::signal + boost::shared_ptr+...
Stultus
> eventManager.Send(ACTOR_RCV_DAMAGE, actorId) // Посылает события указывающее на
> то, что actor #actorId получил урон
смотря с какой стороны посматреть.
что такое ACTOR_RCV_DAMAGE? константа, дефайн, объект идентификатор.
проблема с передачей данных.
eventManager.Send(ACTOR_RCV_DAMAGE)
eventManager.Send(ACTOR_RCV_DAMAGE, arg0)
eventManager.Send(ACTOR_RCV_DAMAGE, arg0,arg1)
eventManager.Send(ACTOR_RCV_DAMAGE, arg0,arg1,arg2)
...
как отслеживать правильный вызов с правильными данными и соответствие типов?
можно для каждого мессаги сделать свой Send.
eventManager.Send<ACTOR_RCV_DAMAGE>(int arg0)  { send_1(ACTOR_RCV_DAMAGE,arg0); }
но так для каждого евента замотаешься.
есть boost::signal.
можно eventManager.signalACTOR_RCV_DAMAGE(...);
если в каком то мести мы знаем ACTOR_RCV_DAMAGE то ничего не мешает заместо этого знать signalACTOR_RCV_DAMAGE.

#4
14:44, 3 янв 2011

Stultus
> Но, пожалуй, основная проблема, которую я не могу решить, и которая обсуждается, в том
> числе на этом форуме, как передавать данные вместе с событиями всем компонентам?
> опустим, GraphicsComponent требует данные от HealthComponent. Критерия всего два,
> данные хотелось бы передавать через LUA и через С++, при этом максимально уменьшить
> связность компонент.

Можно использовать глобальный пул функций, как то так:
std::map<string, void*> function_pool;

HealthComponent при инициализации делает что то типа:

function_pool["GetHealth"] = new boost::function<float(void)>([this]() -> float { return this->health;}); // использую лямбда-функцию, но идея понятна

А GraphicsComponent делает так:

float health = (*reinterpret_cast<boost::function<float(void)>*>(globals::function_pool["GetHealth"]))();
#5
16:03, 3 янв 2011

Немного повозился с системой сигналов и компонентов, пришел к такой системе: (Почти псевдокод)

struct OpaqueComponent {};
struct Component : OpaqueComponent {virtual void OnInit(); virtual void OnExit(); /*и.т.д*/};
struct UpdatableComponent : public Component {virtual void Update();};
template<class T>
struct ComponentProxy : OpaqueComponent, public T {};

Суть в том, что если нужен обновляющийся компонент, то используем UpdatableComponent, иначе Component.
Если компонент не принимает/отсылает сообщение и не обновляется (т.е. этот компонент вручную получает ссылки на нужные компоненты) можно использовать ComponentProxy или наследовать от OpaqueComponent.

В итоге имеем что-то вроде..
{
  ctor() 
  { /*Вызываются например 100000 раз за кадр (i.e. 60 раз у 1500 объектов)*/
    spatialComponent.link(rendercomponent); 
    rendercomponent.link(spatialComponent);
  }
}
....
....
AddComponent(healthComponent /*наследует от Component*/) /*Вызывается например 1 раз за кадр, т.е. можно использовать сигналы/мессаджы*/
...
jetpackComponent;
jetpackEnginesComponent;
jetpackComponent.link(jetpackEnginesComponent);
AddComponent(jetpackComponent); /*Добавляем в runtime при этом не теряем производительности т.к. нет надобности в сигналах*/

Как-то так... (P.s. динамические компоненты делал т.к. они у меня расширяются через LUA)


Так никто и не ответил, Вы сами передаёте Entity/Actor'у компоненты и начальные данные (i.e. mesh/material) или он сам их создает в отдельном factory/конструкторе? Или это вообще не имеет никакого значения? о_о

Ещё один вопрос... Допустим есть тот самый healthComponent и renderComponent, и как советуют здесь и здесь и ещё много где, я создаю между ними связь на уровне compile-time, т.е.

RenderComponent::LinkToHealthComponent(HealthComponentPtr /*shared_ptr или ссылка не важно*/)

Завтра мне понадобиться связь jetPackEffectsComponent <-> RenderComponent, мне либо придётся создавать RenderComponentThatLinksToJetpacksAndHealth либо пихать в RenderComponent

RenderComponent::LinkToJetPackComponent(JetPackPtr /*shared_ptr или ссылка не важно*/)

Но это же гадость... Получается та же уродливая цепь связей или иерархий, но только на уровне компонент... (Я наверное чего-то не понял?)
Пока буду использовать сигналы и мессаджы, а там видно будет...

#6
19:08, 3 янв 2011

Зачем рендер компоненте знать об джетпаке?

#7
20:37, 3 янв 2011

Z
> Зачем рендер компоненте знать об джетпаке?
Я считаю, что незачем, потому и удивляюсь, что в некоторых постах (не в этом конкретном топике) советуют делать сильную связь на уровне compile-time между определёнными компонентами... Или же я просто тупой пример привел с этим джетпаком...

#8
21:36, 3 янв 2011

Stultus
Связъвай как угодно, но рендер компонента должна бъть по возможности только входом, т.е. давать ей примерно имя меша, анимации + время и все.
А если компонент джетпака имеет такие параметръ по дефольту - еще лучше.

#9
14:19, 4 янв 2011

я опять с глупыми вопросами. зачем луа? какую цель преследует ТС, перекладывая функциональную нагрузку с С++ на lua?

added:
редакторы приведённых в примере игр не используют нативные языки прежде всего потому что предполагается, что редактор будут использовать совершенно другие люди, не знакомые с кодом проекта. это сильно снижает гибкость, производительность и удобство поддержки, так как ту же самую функциональность на нативном языке профессиональный, знакомый с кодом проекта, мог бы реализовать эффективнее. у вас тоже предполагается большая команда, работающая над проектом, либо система скриптования требуется просто потому что это считается модно и круто?

проблемой поднималась слишком сильная связность компонент. чего вы хотите добиться подобной системой скриптования? что эта связность перейдёт с нативного кода на скриптовый?

#10
14:31, 4 янв 2011

Suslik
Может бъть потому, что нативнъй код труднее дебагать, не охота явно работать с памятью (снижает производительность труда), ефективности в игровом коде топовой не надо, есть closures и можно легко запускать софтовъе threads?

#11
14:40, 4 янв 2011

Z
> Может бъть потому, что нативнъй код труднее дебагать
написать debug helper куда эффективнее, чем скриптовую систему. честно.

> не охота явно работать с памятью (снижает производительность труда)
в любой наколенной поделке, называющейся игровым движком первым же из предназначений является автоматический менеджмент ресурсов. создаются объекты фабриками, убиваются рефкаунтерами или ещё как-то, но в любом случае этот менеджер будет не менее эффективен(удобен, бла-бла-бла), чем менеджер памяти любого скриптового языка.

> ефективности в игровом коде топовой не надо
я уже сталкивался с достаточным количеством горе-программеров, которые своей орхетектурой убивали производительность просто за счёт оверхеда. передачка сообщений туда-сюда между тысячей юнитов(РТС) - вполне реалистичный сценарий использования с вполне реалистично светящим оверхедом.

> есть closures
о-хо-хо. без паттернов(iterator? abstract class?) обойтись по твоим словам можно, а без closures - нельзя? прошу прощения, я с closures знаком очень поверхностно и могу ошибатсья, но первое знакомство сложило впечатление синтаксического сахара. причём с сомнительной областью применения.

> софтовъе threads
извини, я не понял, что ты этим хотел сказать. но если это как-то связано с распараллеливанием, то ты ведь не хочешь сказать, что в нативном языке нельзя сделать более эффективный(бла-бла) инструментарий для распараллеливания, нежели чем в скриптовом?

Z
извини, я тебя уважаю, все дела, no offense, если что. просто иногда ловлю себя на мысли, что спорю не для того, чтобы понять или кому-то что-то доказать, а просто чтобы спорить. просьба не воспринимать мои аргументы как наезд, у меня по жизни такая манера говорить.

#12
15:04, 4 янв 2011

Suslik
>написать debug helper куда эффективнее, чем скриптовую систему. честно.
Мм.. что?
В скрипте можно на лету менять почти все, перезагружать сам скрипт. Нейтив такое не может. Можно евалюейтить вообще все и на стороне дебаггера, что в нейтиве увъ тоже невозможно.
Можно очень просто профайлить, запоминать вообще трейс последних инструкций и смотреть что там да как. Тут нейтив тоже не помогает никак.

> но в любом случае этот менеджер будет не менее эффективен(удобен, бла-бла-бла),
> чем менеджер памяти любого скриптового языка.
A garbage collection есть? Там просто нет явного въделения памяти и работъ с ней, вот и все.

>я уже сталкивался с достаточным количеством горе-программеров, которые своей орхетектурой убивали производительность просто за счёт оверхеда.
Кривъе руки нейтив тоже не полечит, да и не дело мне честно давать советъ как жить с кривъми руками... жизнь, так сказать научит.

> причём с сомнительной областью применения.
Они очень удобнъ, их все кому не лень юзают (flash, етц.). В рендерере может им не место, но в игровом коде применения есть, даже хотя для реализации софтовъх ниток.
Образовъватся очень полезно!

> извини, я не понял
Реализация threads внутри скрипта, просто преръвая въполнение одного скриптового кода и передача управление другому, как ето делает тот же OS, только все ето внутри скрипта, средствами скрипта.
Ефективнее в плане менежмента и юзаемой памяти + контроль над въполнением, что нейтив тредс тебе не дадут.

#13
15:18, 4 янв 2011

Z
> Мм.. что?
debug helper - это такая фича серьёзных сред разработки, позволяющая представлять отладочную информацию в удобном виде, реализуется специальными скриптами для дебаггера. позволяет, например смотреть содержимое std::vector напрямую, не видя его "кишков". я, если честно, сам подобные скрипты не писал, но что-то мне подсказывает, что многие перечисленные тобой фичи на них можно реализовать куда проще, чем при помощи системы скриптования.

> В скрипте можно на лету менять почти все, перезагружать сам скрипт.
я, наверное, как-то по-другому дебажу, но мне никогда не хотелось перезапустить какую-то из подсистем из дебаггера.

> A garbage collection есть? Там просто нет явного въделения памяти и работъ с ней, вот и все.
смысла в garbage collector'е(вообще говоря, очень сложной системе с существенным оверхедом) нет, если использутся специфичный именно для данной системы менеджер ресурсов. ему куда лучше знать, когда подкружать и выгружать ресурсы, чем garbage collector, который о логике не знает вообще ничего и работает с абстрактными областями памяти.

> Кривъе руки нейтив тоже не полечит, да и не дело мне честно давать советъ как жить с кривъми руками... жизнь, так сказать научит.
вот, именно к этому я и клоню - правильная написанная система скриптования как раз более-менее спасает от кривых рук, которые могут её использовать. но если ты уверен, что работаешь один/в небольшой команде доверенных программистов, то я по-прежнему не вижу смысла делать защиту от кривых рук. //здесь руки человека/команды, который решает написать систему скриптования априори считаются прямыми

> Они очень удобнъ, их все кому не лень юзают (flash, етц.). В рендерере может им не место, но в игровом коде применения есть, даже хотя для реализации софтовъх ниток.
> Образовъватся очень полезно!
может быть, я здесь сразу сделал оговорку, что знаком с ними лишь поверхностно.

> Реализация threads внутри скрипта, просто преръвая въполнение одного
> скриптового кода и передача управление другому, как ето делает тот же OS,
> только все ето внутри скрипта, средствами скрипта.
не аргумент. нативные механизмы синхронизации куда эффективнее и гибче, чем менеджмент тредов, в которых выполняются скрипты.

таким образом, пока я принимаю как аргументы в пользу системы скриптования следующие факты:
- если нет уверенности в кривизне рук, использующих написанный код. с этого я и начал.
- волшебные closures. буду надеяться, что они того стоят. если без них действительно так трудно жить, ну что ж, ок.

я что-то упустил?

#14
15:41, 4 янв 2011

Suslik
> что многие перечисленные тобой фичи на них можно реализовать куда проще, чем
> при помощи системы скриптования.
Знаю я ету фичу, она вообще не помогает, когда нужно въполнять код в режиме дебага примерно. И многое другое.

> я, наверное, как-то по-другому дебажу, но мне никогда не хотелось перезапустить
> какую-то из подсистем из дебаггера
Делаем скрипт юнита, напоролись на баг - фиксим, релоуд скрипта, продолжаем. Очень бъстръе итерации и фикс сложнъх для повторения ситуаций. В нейтиве же приходится делать перезапуск, что особенно неприятно если ситуация где баг - очень редкая.

> смысла в garbage collector'е(вообще говоря, очень сложной системе с
> существенным оверхедом) нет, если использутся специфичный именно для данной
> системы менеджер ресурсов. ему куда лучше знать, когда подкружать и выгружать
> ресурсы, чем garbage collector, который о логике не знает вообще ничего и
> работает с абстрактными областями памяти.
Скажи ето 90% програмистов, которъе работают именно в среде с GC. Потом подумай, почему так сложилось.
Потому что там КПД создания софта въше и ето одна из причин.

>о я по-прежнему не вижу смысла делать защиту от кривых рук.
Я нигде не говорил что ето защита от кривъх рук, ето удобство и более бъстрая работа в некой части разработки игр, а именно в гейм коде.

> нативные механизмы синхронизации куда эффективнее и гибче, чем менеджмент
> тредов, в которых выполняются скрипты.
При чем сдесь механизмъ синхронизации? Сдесь о менежменте тредов. Примерно штук 100-200-300 одновременно. При том, охота чтобъ они гарантированно засъпали и просъпались в нужную милисекунду и работали в нашем каком-то времени (game time).
Достаточно специфично и невъполнимо для OS-а.

Страницы: 1 2 3 4 5 6 7 Следующая »
ПрограммированиеФорумОбщее

Тема в архиве.