Sign in to follow this  
photo

Очерёдность вызова методов init() у AppWorldLogic и custom-компонента

Recommended Posts

Добрый день, сотрудники компании Unigine.
Использую custom-компонент TanksOverflowLogic:

class TanksOverflowLogic : public InteractiveMode
{
public:
	COMPONENT(TanksOverflowLogic, InteractiveMode);

	PROP_NAME("tanks_overflow_logic");
	PROP_AUTOSAVE(1);

private:
	void on_start(int is_training_mode) override;
	void on_stop() override;
};

Компонент InteractiveMode, подобный реализованному в проекте "oiler_refinery_sample" (приведу в урезанном варианте, для наглядности поста):

class InteractiveMode : public LogicController
{
public:
	COMPONENT(InteractiveMode, LogicController);

	PROP_PARAM(Node, inventory);
	PROP_PARAM(Node, checklist);
	PROP_PARAM(Node, task_helpers);
	PROP_PARAM(Node, interactive_borders);

protected:
	void on_init() override;
...
};

И компонент LogicController, так-же полностью взят из проекта "oiler_refinery_sample" (приведу в урезанном варианте, для наглядности поста):

class LogicController : public ComponentBase
{
public:
	COMPONENT(LogicController, ComponentBase);

	COMPONENT_INIT(init, 3);

	PROP_PARAM(Node, player_spawn_node)
	PROP_PARAM(Node, start_point);
	PROP_PARAM(Node, floor_node);

	// logic control methods
	void start(int is_training_mode);
	void stop();

protected:
	virtual void on_init() {};
	virtual void on_start(int is_training_mode) {};
	virtual void on_stop() {};
...
};

Соответственно, имя такую иерархию зависимостей между компонентами я хочу при инициализации мира, запустить режим TanksOverflowLogic. Для этого я создал узел "tanks_overflow" (NodeDummy) в редакторе, повесил на него свойство "tanks_overflow_logic", навешал параметры (inventory, checklist, task_helpers и т.д.). Класс AppWorldLogic имеет внутреннюю переменную-указатель "tanks_overflow_logic" и метод "setTanksOverflowInteractiveMode()":

class AppWorldLogic : public Unigine::WorldLogic {
public:
	AppWorldLogic();
	virtual ~AppWorldLogic();

	virtual int init();
...	
private: 
...
	TanksOverflowLogic *tanks_overflow_logic;
public:
	void setTanksOverflowInteractiveMode(int training_mode);
};

В методе "AppWorldLogic::init()" я инициализирую указатель "tanks_overflow_logic" и вызываю метод "setTanksOverflowInteractiveMode(1)" (многоточие символизирует часть кода, которая не важна в данный момент). lo:

void AppWorldLogic::setTanksOverflowInteractiveMode(int training_mode)
{
...
  	NodePtr logic_layer = Editor::get()->getNodeByName("logic_layer");
	if (!logic_layer)
	{
		Log::error("APP WORLD LOGIC ERROR: logic layer not found\n");
		return 0;
	}
  
	tanks_overflow_logic = ComponentSystem::get()->getComponentInChildren<TanksOverflowLogic>(logic_layer);
...
	setTanksOverflowInteractiveMode(1);
}

При этому дерево дочерних узлов у узла "logic_layer"  имеет вид представленный в файле "logic_layer.png":

image.png.53a79db6a23454f280de9cb2af6bb2cb.png

Из которого видно, что узел "tanks_overflow" имеет дочерний узел "mission" со свойством "mission" и следующими параметрами параметрами следующего вида:

image.png.7ff6385fe28137e5e7c862627ca4d0bd.png

, где параметр "Function" = "all" и "Hierarchy Based" = "true", что говорит о том, что компонентом должны отслеживаться состояния всех дочерних узлов (в данном случае, единственного - "open_the_valve").

Метод "AppWorldLogic::setTanksOverflowInteractiveMode(int training_mode)" имеет вид:

void AppWorldLogic::setTanksOverflowInteractiveMode(int training_mode)
{
...
	tanks_overflow_logic->start(training_mode);
}

и метод LogicController::start():

void LogicController::start(int is_training_mode)
{
...
	mission_handler_id = mission->setOnStateChangedListener(std::bind(&LogicController::on_mission_state_changed_handler, this, std::placeholders::_1, std::placeholders::_2));
...
}

В этой строке я получаю NULL-исключение, для внутренней переменной mission. Инициализация переменно mission происходит в методе "LogicController::init()" следующим образом:

	mission = getComponentInChildren<Mission>(node);

, но отладка показывает, что я не попадаю в LogicController::init(), т.е. инициализация компонента LogicLayer ещё не произошла, а я пытаюсь вызвать его метод "start()". При этом в AppSystemLogic:init() делаю инициализацию системы компонентов:

int AppSystemLogic::init()
{
	ComponentSystem::get()->initialize();
	return 1;
}

Вопрос:

  1. Разве метод AppWorldLogic::init() отрабатывает позже метода init() у custom-компонента? Ведь я провожу инициализацию системы компонент в классе AppSystemLogic::init() и ожидаю, что при вызове AppWorldLogic методы init() для всех компонент уже отработали. Что я не верно понимаю, что приводит к такому результату?
     

Share this post


Link to post

Здравствуйте.

При текущей реализации все компоненты инициализируются одновременно с AppWorldLogic. Поэтому нельзя вызывать старт миссии в AppWorldLogic::init(), т.к. не все компоненты завершили инициализацию. Можно активировать миссию в первом AppWorldLogic::update():

if (!tanks_overflow_logic->isActive())
    tanks_overflow_logic->start(training_mode);

 

Share this post


Link to post

Спасибо за совет, но даже при такой реализации, вызывая tanks_overflow_logic->start() из AppWorldLogic::update(), я так и не попадаю перед этим в LogicController::init() для инициализации используемой в start() переменной "mission". Т.о. у меня уже начинает выполняться AppWorldLogic::update(), а init-компонента ещё не отработал. 

Edited by nikolaj.kormushkin

Share this post


Link to post

Может есть какой-то механизм гарантирующий мне, что init()-ы всех  custom-компонент уже отработали?

Share this post


Link to post

Здравствуйте.

Я создал указанную Вами структуру миссии. Полностью повторил tanks_overflow_logic, mission и open_the_valve. Но в параметры tanks_overflow_logic добавил Floor Node, Task Helpers и Interactive Borders, которые в Вашем примере не были указаны. Для теста эти параметры аналогичны тем, которые указаны в generator_logic. После этих изменений данная миссия стала корректно запускаться. При текущей реализации данные параметры являются необходимыми. В противном случае LogicController не сможет корректно инициализироваться, т.к. в функции init() есть проверка:

if (!floor_node)
    {
#if DEBUG
        Log::error("Logic Controller error: floor_node is null, please check %s\n", node->getName());
#endif
        return;
    }

В логе должна будет отобразиться ошибка. Проверьте, пожалуйста, этот момент. Также при необходимости можно убрать неиспользуемые параметры в логике. Но следует убедиться, что будут также исправлены все компоненты, которые были связаны с ними.

Share this post


Link to post
On 2/6/2020 at 3:22 PM, karpych11 said:

Здравствуйте.

Я создал указанную Вами структуру миссии. Полностью повторил tanks_overflow_logic, mission и open_the_valve. Но в параметры tanks_overflow_logic добавил Floor Node, Task Helpers и Interactive Borders, которые в Вашем примере не были указаны...

Добрый день, спасибо, что указали на мою невнимательность в коде метода "LogicController::init()". Действительно, точку останова я поставил дальше этих return-ов и судил по ней, что я не попадаю в метод init() custom-компонента.
Но к сожалению после исправления данных недочётов я наткнулся на проблему которую не знаю как отладить. После завершения метода AppWorldLogic::update() происходит зависание системы минут на 10 (так, что не происходит никакой реакции на нажатие мыши или клавиатуры), после чего появляется следующая ошибка:

image.thumb.png.dc655c1784f8f9e89ac0fe7b05a88371.png

К сожалению при переходе через F10 (в MS Studio 2017) дальше метода update() выполнение передаётся на скрытые методы движка и я не имею возможности отладить процесс зависания:
  image.png.d2429828060e4a3aced83a8f8f43c359.png

Вопросы:

  1. Как можно отладить причину зависания в данной ситуации? Можно-ли отправить Вам проект для диагностики? Возьмётесь-ли вы диагностировать проблему?
  2. Можно попросить Вас выслать мне проект который Вы создали для диагностики первоначальной проблемы (если да, то загрузите его пожалуйста на Ваш ftp-сервер)?

Share this post


Link to post

Николай, здравствуйте!

Если дебаггер приказал долго жить, то можно отлаживаться по-старинке: выводя в лог сообщения или же просто комментировать код и смотреть в какой момент всё запустится снова.

Можете прислать нам проект, но, боюсь, что посмотрим мы его только на следующей неделе.

Спасибо!

Share this post


Link to post
11 minutes ago, silent said:

Николай, здравствуйте!

Если дебаггер приказал долго жить, то можно отлаживаться по-старинке: выводя в лог сообщения или же просто комментировать код и смотреть в какой момент всё запустится снова.

Можете прислать нам проект, но, боюсь, что посмотрим мы его только на следующей неделе.

Спасибо!

Спасибо, отправлю Вам чуть позже проект. Я ещё спросил, может-ли karpych11 передать свой рабочий проект, который он создал для анализа причины ошибок?

Share this post


Link to post
Quote

Я ещё спросил, может-ли karpych11 передать свой рабочий проект, который он создал для анализа причины ошибок?

К сожалению, он был удалён, так что присылать особо нечего.

Share this post


Link to post

Здравствуйте.

Проблема оказалась связана с компонентой tooltip, которая используется в подсказках к заданиям. В ней линия от предмета к надписи отображается с помощью WorldSplineGraph. Его перестроение происходит при вызове connector->rebuild(1). Если этот метод вызывается в первых кадрах при запуске, то происходит зависание приложения. Для решения этой проблемы желательно отказаться от использования данного объекта, и создавать соединяющую линию самому. Также возможно сделать ожидание нескольких кадров в компоненте следующим образом:

в Tooltip.h добавить int wait_frames = 5;

в Tooltip.cpp добавить:
 

void Tooltip::update()
{
	if (!current_state)
		return;

	if (wait_frames != 0)
		wait_frames--;
	...
	...

	if (segment)
	{
		if (wait_frames == 0)
		{
			Mat4 connector_iwt = connector->getIWorldTransform();
	...
	...

 

Share this post


Link to post
19 hours ago, karpych11 said:

Здравствуйте.

Проблема оказалась связана с компонентой tooltip, которая используется в подсказках к заданиям. В ней линия от предмета к надписи отображается с помощью WorldSplineGraph. Его перестроение происходит при вызове connector->rebuild(1). Если этот метод вызывается в первых кадрах при запуске, то происходит зависание приложения. Для решения этой проблемы желательно отказаться от использования данного объекта, и создавать соединяющую линию самому. Также возможно сделать ожидание нескольких кадров в компоненте следующим образом:

в Tooltip.h добавить int wait_frames = 5;

в Tooltip.cpp добавить:
 


void Tooltip::update()
{
	if (!current_state)
		return;

	if (wait_frames != 0)
		wait_frames--;
	...
	...

	if (segment)
	{
		if (wait_frames == 0)
		{
			Mat4 connector_iwt = connector->getIWorldTransform();
	...
	...

 

Огромное спасибо за диагностику проблемы.

  1. Скажите пожалуйста, я мог-бы провести диагностику своими силами или для диагностики Вам потребовались исходные коды движка? Если да, то какова методология действий при такой ситуации? Меня ограничило то, что я не смог в отладке (через F10) пройти дальше AppWorldLogic::update(), как показано на скрине выше - выполнение по стэку вело на методы в исходнике enginecallbacks.cpp движка. Может у Вас есть советы, что следует проделать дополнительно в таких ситуациях для отладки? 
  2. А как ещё можно реализовать отображение связи между объектом и сообщением подсказки помимо WorldSplineGraph, при этом иметь изящную(изогнутую, не прямую) линию?
Edited by nikolaj.kormushkin

Share this post


Link to post

Здравствуйте.

В данном случае исходный код движка не понадобился. Проблемная компонента была найдена с помощью поочередного отключения логики миссии. Далее уже внутри компоненты отключалась логика линии, стрелки и таблички. Таким образом была выявлена проблема зависания. В AppWorldLogic::Update() начинался старт миссии, что приводило к включению подсказки. На этом моменте происходило зависание в движке, поэтому Вы не могли при отладке пройти дальше этого места.

Реализацию линии можно посмотреть в демо Superposition. Скопируйте его как проект. Там есть ObjTooltip.h и ObjTooltip.cpp. Линия создается с помощью ObjectMeshDynamic. Вам потребуется только тот код, который работает с переменной obj_line.

Share this post


Link to post
Sign in to follow this