Russian Qt Forum

Программирование => Общий => Тема начата: Igors от Декабрь 16, 2010, 16:13



Название: Пример проектирования
Отправлено: Igors от Декабрь 16, 2010, 16:13
Добрый день

Заказчик попросил улучшить качество UI/preview (изначально писано не мной). Работая над задачей, подумалось: вот хороший пример "живого" проектирования классов и их взаимодействия. С одной стороны - не слишком сложно, классов немного и они понятны. Не требуется знания предметной области. С др. стороны - решить проблемы не так уж просто (по крайней мере для меня).

Краткое описание
Есть N окон (обычно немного, до 10) в которых рисуются 3D объекты. Любое окно может содержать любое кол-во объектов. Есть 2 способа отображения - с использованием OpenGL и без, пользователь может выбрать один из них для каждого окна индивидуально. Объекты могут отображаться текстурированными, для каждого объекта текстура назначается индивидуально. В любой момент пользователь может открыть/закрыть любое из окон, добавить/удалить объекты, изменить текстуру любого объекта. 

Принципиальные классы
Object3D - объект который рисуем

Texture - указатель на Image + файл имеджа + набор параметров наложения (фильтр, сдвиг, поворот, способ наложения и др)

Image - просто картинка загруженная в память

DrawEngine3D - базовый класс для рисования объектов, с текстурой или без. Каждое окно имеет свою DrawEngine

DrawEngine_Soft - порожден от DrawEngine3D, умеет рисовать без OpenGL
DrawEngine_Hard - то же но с OpenGL

GL_TextureID - ID имеджа загруженного в OpenGL. Этот ресурс должен быть создан для DrawEngine_Hard (используя пиксели класса Image) перед началом рисования и освобожден когда необходимость в отрисовке данного имеджа отпала

-------- Требуется ----------

Оптимизировать использование ресурсов (классы Image и GL_TextureID). Примеры: многие текстуры используют один и тот же файл картинки, значит она должна быть загружена 1 раз и выгружена когда никто больше ее не использует. Также и OpenGL текстура должна создаваться 1 раз (а не для каждого окна и не для каждой текстуры) и освобождаться автоматычно.

-----------------

Охотно послушаю тех кто любит (и умеет) проектировать - ну или по крайней мере прочитал много толстых книг  :)

Спасибо


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 16, 2010, 16:33
boost::shared_ptr
QSharedDataPointer+QSharedData


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 16, 2010, 17:03
boost::shared_ptr
QSharedDataPointer+QSharedData
Ну что нужно "шерить" - это всем понятно. Но вот кто кого и как "шерит" ?


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 16, 2010, 17:10
Ну что нужно "шерить" - это всем понятно. Но вот кто кого и как "шерит" ?
Код
C++ (Qt)
class ImageData : public QSharedData // хранит сами данные (приватный класс)
{
...
};
 
class Image // хранит указатель на данные (публичный класс)
{
public:
...
private:
QSharedDataPointer<ImageData> d;
};
 

Все же описано в документации. Или я не вижу в чем проблема?


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 16, 2010, 17:39
Все же описано в документации. Или я не вижу в чем проблема?
Кстати, я дал такое же имя ImageData  :)
А проблема в том кто (и как) должен "владеть". Напр. пусть это класс Texture - не годится, т.к. мы не знаем захочет ли хоть одна DrawEngine это рисовать. А сама DrawEngine еще меньше подходит - ведь 100 Texture(s) могут использовать 1 Image

Вопрос по взаимодействию классов, техника shared не проблема


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 16, 2010, 17:53
Код
C++ (Qt)
{
Image i; // Создался объект Image, внутри создался объект Image Data
// Владеет данными объект i
}
// Вышли из области видимости, объект i разрушился, он использовал данными один - данные разрушились.
 

Код
C++ (Qt)
{
Image i;
{
Image i2;
i2.load( ... );
i = i2; // i и i2 указывают на одни и те-же данные, но объекта два
}
// Объект i2 разрушился, но данные остались, т.к. i на них ссылается.
}
 

Владельцем данными картинки (ImageData) является сам объект картинки (Image). Если этот объект разрушается и он монопольно этими данными пользуется, то разрушаются и данные.


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 16, 2010, 18:01
Код
C++ (Qt)
// Вышли из области видимости, объект i разрушился, он использовал данными один - данные разрушились.
 
Ресурсы должны быть загружены если они нужны для рисования хотя бы в 1 открытом окне. Напр. если пользователь вращает модель, то будет дороговато грузить картинку с диска и распаковывать ее на каждый move мыши.


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 16, 2010, 18:20
Сообразил, тут вопрос в реализации менеджера ресурсов.
На примере картинок... Все картинки грузятся методом специального объекта - менеджера картинок.
Этот метод проверяет, если такая картинка не загружена, то создается новый объект Image, в него загружается картинка, помещается в кэш и возвращается объект Image. Если же такой объект уже есть в кэше, то возвращается его копия.
Код
C++ (Qt)
class ImageManager
{
public:
Image load( const QString &name );
 
private:
QMap<QString, Image> m_cache;
};
 

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



Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 16, 2010, 18:53
На примере картинок... Все картинки грузятся методом специального объекта - менеджера картинок.
Это хороший подход, но по существу это "кэш", и значит надо как-то контролировать число помещаемых картинок, да и общий размер данных. То есть мы избавляемся от одних забот но получаем другие.

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


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 16, 2010, 19:40
Хотя кэш выглядит значительно проще для реализации, мне кажется здесь нет оснований его применять - ведь имеются/определены все данные о том что должно быть загружено и что нет.
Как-то опять непонятно.  :)
Какие нужны основания? Для централизованного управления ресурсами нужны эти самые средства. В том или ином виде. Если ничего не контролировать, то и проконтролировать ничего не получиться.


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 16, 2010, 19:54
Как-то опять непонятно.  :)
Какие нужны основания? Для централизованного управления ресурсами нужны эти самые средства. В том или ином виде. Если ничего не контролировать, то и проконтролировать ничего не получиться.
Я хочу иметь загруженными только те ресурсы что нужны для рисования. Число ссылок стало = 0, освобождаем ресурс. Кэш не совсем то. Напр. картинка может сидеть в кэше хотя ее уже никто не пользует. Или наоборот, емкость кэша недостаточна - и тогда при рисовании придется грузить (апельсины бочками). Кэш очень хорош если данные идут потоком и могут повторяться. Но это не мой случай.


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 16, 2010, 20:10
Без централизованного получения/создания ресурсов все равно не обойтись.
А для кэше можно попробовать использовать "слабые указатели". Тогда в кэше не будет объекта удерживающего данные и они будут разрушаться после разрушения последнего владельца.
Кэш подчищать придется только от уже невалидных указателей.


Название: Re: Пример проектирования
Отправлено: ufna от Декабрь 16, 2010, 20:29
+1 к BRE

Все задачи, описанные в сабже, решаются за счет ввода менеджера ресурсов. Это очень важная часть движка.


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 16, 2010, 20:47
А для кэше можно попробовать использовать "слабые указатели". Тогда в кэше не будет объекта удерживающего данные и они будут разрушаться после разрушения последнего владельца.
Это мне и нужно. Но кто и как должен удерживать (или не удерживать) данные? (см. пост #4)
Где взять "владельца"?

Все задачи, описанные в сабже, решаются за счет ввода менеджера ресурсов. Это очень важная часть движка.
Так давайте введем чудесный менеджер ресурсов  :)  Расскажите мне что он должен делать, а я уж как-нибудь реализую.


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 16, 2010, 21:06
Набросал немного кода, посмотри (компилябельно):
Код
C++ (Qt)
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <string>
#include <map>
#include <iostream>
 
 
class Resource
{
public:
Resource() {}
Resource( const std::string &name ) : m_data( name ) {}
std::string m_data;
};
 
typedef boost::shared_ptr<Resource> ResourcePtr;
 
class ResourceManager
{
public:
ResourcePtr load( const std::string &name );
 
void clearCache();
void dumpCache() const;
 
private:
typedef boost::weak_ptr<Resource> SResourcePtr;
typedef std::map<std::string, SResourcePtr> MapCache;
MapCache m_cache;
};
 
ResourcePtr ResourceManager::load( const std::string &name )
{
MapCache::iterator it = m_cache.find( name );
if( it != m_cache.end() )
{
ResourcePtr o = it->second.lock();
if( o )
return o;
else
m_cache.erase( it );
}
 
ResourcePtr r( new Resource( name ) );
m_cache[ name ] = SResourcePtr( r );
return r;
}
 
void ResourceManager::clearCache()
{
MapCache::iterator it;
for( it = m_cache.begin(); it != m_cache.end(); ++it )
{
if( !it->second.lock() )
m_cache.erase( it );
}
}
 
 
void ResourceManager::dumpCache() const
{
std::cout << "Cache num: " << m_cache.size() << std::endl;
MapCache::const_iterator it;
for( it = m_cache.begin(); it != m_cache.end(); ++it )
{
ResourcePtr o = it->second.lock();
std::cout << "key: " << it->first << " -> " << ( ( o )? o->m_data : "unload" ) << std::endl;
}
}
 
 
int main( int argc, char **argv )
{
ResourceManager man;
 
{
ResourcePtr o1 = man.load( "AAA" );
ResourcePtr o2 = man.load( "BBB" );
ResourcePtr o3 = man.load( "BBB" );
 
std::cout << o1->m_data << " " << o2->m_data << " " << o3->m_data << " " << std::endl;
}
 
ResourcePtr o4 = man.load( "DDD" );
man.dumpCache();
man.clearCache();
man.dumpCache();
 
   return 0;
}
 


Название: Re: Пример проектирования
Отправлено: ufna от Декабрь 16, 2010, 21:13
Смотри gamedev.ru и dtf.ru - уже на сотни раз перетерто с различными вариациями :)


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 16, 2010, 21:54
Набросал немного кода, посмотри (компилябельно):
Код
C++ (Qt)
MapCache::iterator it;
for( it = m_cache.begin(); it != m_cache.end(); ++it )
{
if( !it->second.lock() )
m_cache.erase( it );
}
 
Не уверен что это не вылетит, может лучше так
Код
C++ (Qt)
MapCache::iterator it;
for( it = m_cache.begin(); it != m_cache.end(); )
{
if( !it->second.lock() )
m_cache.erase( it++);
               else
                       it++;
}
 
Но не суть, это просто к слову.

За пример конечно спасибо, но Вы предлагаете "общий" способ решения. "Да чего там разбираться с какими-то классами Igors, вот есть прекрасная техника которая отлично работает в сотнях случаев - и в моей практике тоже!".  :)  Я ничего не имею против кэширования, но это другая задача. Я хочу реализовать четко reference/dereference баланс. а не кэш. Резоны "почему" у меня есть, но давайте не терять время на их обсуждение, к делу это не относится. Спасибо.




Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 16, 2010, 22:06
Не уверен что это не вылетит, может лучше так
А в чем профит?

Я ничего не имею против кэширования, но это другая задача. Я хочу реализовать четко reference/dereference баланс. а не кэш.
А как без кэширования (в том или ином его виде) можно определить: загружен ресурс или нет?  ::)
Кто-то должен за этим следить.

Насчет общего примера... А мне нужно было обязательно реализовать класс Image? Я вообще-то идею пытался показать.


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 17, 2010, 10:42
Не уверен что это не вылетит, может лучше так
А в чем профит?
У Вас получается что в цикле ++ применяется к итератору после его erase, и это может быть небезопасно

А как без кэширования (в том или ином его виде) можно определить: загружен ресурс или нет?  ::)
Кто-то должен за этим следить.
Ну попробуем упростить - пусть нет окон и машин рисования. Тогда все тривиально - текстуры шерят имеджи. Значит надо как-то учесть машины

Насчет общего примера... А мне нужно было обязательно реализовать класс Image? Я вообще-то идею пытался показать.
Конечно нет, незачем терять время на мелкие брызги/детали. Я имел ввиду что предложенное решение никак не зависит от специфики задачи (оно общее). Ничего плохого в этом нет, но оно и будет работать "в свою силу". Кэш не гарантирует что все будет загружено во время интерактивных операций. Также какие-то картинки могут сидеть в кэше даже если они уже не используются. При размерах 50-100 Mb на картинку это "чревато". Поэтому нет смысла быстро делать первую версию которая создает потенциальные проблемы.


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 17, 2010, 11:14
У Вас получается что в цикле ++ применяется к итератору после его erase, и это может быть небезопасно
::)
Никакого erase итератора не происходит, удаляется элемент на который указывает итератор.

Ну попробуем упростить - пусть нет окон и машин рисования. Тогда все тривиально - текстуры шерят имеджи. Значит надо как-то учесть машины
Как только появляются машины, сразу встает вопрос о том, что какая-то машина уже загрузила такую картинку, она есть в памяти и грузить ее повторно не нужно.  ;D

Кэш не гарантирует что все будет загружено во время интерактивных операций.
Это как?  :)
Кеш вообще не занимается вопросами загрузки.

Также какие-то картинки могут сидеть в кэше даже если они уже не используются.
С чего это?  :)

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


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 17, 2010, 14:58
Никакого erase итератора не происходит, удаляется элемент на который указывает итератор.
Конечно, но нет гарантий что ++ умеет переходить на следующий элемент "самостоятельно". Он может использовать элемент на который ссылается - а Вы его уже удалили.

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

Как только появляются машины, сразу встает вопрос о том, что какая-то машина уже загрузила такую картинку, она есть в памяти и грузить ее повторно не нужно. 
Ну а как насчет "выделить сущности", "(глубоко) продумать взаимодействие" и многое др. о чем так много пишут?  :) Давайте вместе выделять/продумывать (вместо того чтобы сваливаться в шаблонные решения которые в данном случае не очень подходят).


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 17, 2010, 15:18
Конечно, но нет гарантий что ++ умеет переходить на следующий элемент "самостоятельно". Он может использовать элемент на который ссылается - а Вы его уже удалили.
Для чего эти предположения? Стандарт же.  :)

Кто и как возьмет на себя ответственность за удаление ресурса? Не вижу unlock() в Вашем коде - значит вытеснение происходит автоматически (емкость кэша). А если нет - то где предел до которого кэш может расти?
Сколько ресурсов необходимо и используются (находятся в памяти), столько записей и будет в кэше. Как только все кто используют ресурс его освобождают данные ресурса удаляются из памяти, а ссылка на ресурс в кэше становиться невалидной и легко проверяется. А если немного модифицировать код (несколько строк), как я писал выше, то можно добиться того, что при разрушении ресурса ссылка на него удалялась бы и из кэша.

Приведенный мной код у тебя не компилируется? С чем то новым всегда проще разбираться с работающим кодом.

Ну а как насчет "выделить сущности", "(глубоко) продумать взаимодействие" и многое др. о чем так много пишут?  :) Давайте вместе выделять/продумывать (вместо того чтобы сваливаться в шаблонные решения которые в данном случае не очень подходят).
Решения подходят.  :) И пообсуждать/повыделять можем. Только вначале нужно задачу сформулировать.  ;)


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 17, 2010, 16:01
Для чего эти предположения? Стандарт же.  :)
Хмм.. вот буквально вчера: итерирую std::set. При итерации вызываю метод который удаляет из того же set. Вылетает.

Сколько ресурсов необходимо и используются (находятся в памяти), столько записей и будет в кэше. Как только все кто используют ресурс его освобождают данные ресурса удаляются из памяти, а ссылка на ресурс в кэше становиться невалидной и легко проверяется.
Кто и как захватывает и освобождает? Простейшая схема:

- попросил менеджера дать ресурс - получил
- попользовался
- освободил

Все хорошо но при этом ресурс грузится/выгружается каждый раз. А если не так то как?


Название: Re: Пример проектирования
Отправлено: twp от Декабрь 17, 2010, 17:22
Цитировать
Набросал немного кода, посмотри (компилябельно):
Код
C++ (Qt)
MapCache::iterator it;
for( it = m_cache.begin(); it != m_cache.end(); ++it )
{
if( !it->second.lock() )
m_cache.erase( it );
}
 
Не уверен что это не вылетит, может лучше так
Код
C++ (Qt)
MapCache::iterator it;
for( it = m_cache.begin(); it != m_cache.end(); )
{
if( !it->second.lock() )
m_cache.erase( it++);
else
it++;
}
 
Но не суть, это просто к слову.
не знаю как в std но в Qt в первом отрывке будет гарантированный вылет, я обычно так делаю:
Код
C++ (Qt)
MapCache::iterator it = m_cache.begin();
while (it != m_cache.end()) {
if(!it->second.lock())
it = m_cache.erase(it);
else
++it;
}
 


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 17, 2010, 18:41
Кто и как захватывает и освобождает? Простейшая схема:

- попросил менеджера дать ресурс - получил
- попользовался
- освободил

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

Код
C++ (Qt)
{
Image i1 = ResMan::loadImage( "image1" );
 
// попользовались
}
// Здесь, если нигде больше картинка image1 не используется, то она выгрузиться.
// Если используется, останется в памяти
 

Код
C++ (Qt)
{
Image i1 = ResMan::loadImage( "image1" );
myImages.append( i1 );
}
 
{
Пользуемся картинками из коллекции myImages
Image i1 = myImages[ indexImage1 ];
Image newImage = i1;
}
 
{
myImages.remove( indexImage1 ); // Если эта картинка в локальной колекции не нужна, то удаляем ее.
// Если эта картинка больше нигде не используется, она выгрузиться автоматически
}
 


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 17, 2010, 18:58
У Вас получается что в цикле ++ применяется к итератору после его erase, и это может быть небезопасно
не знаю как в std но в Qt в первом отрывке будет гарантированный вылет...
Признаю, с итератором действительно так лучше не поступать. Могут быть не предсказуемые вещи . :)


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 18, 2010, 12:25
Код
C++ (Qt)
{
Image i1 = ResMan::loadImage( "image1" );
 
// попользовались
}
// Здесь, если нигде больше картинка image1 не используется, то она выгрузиться.
// Если используется, останется в памяти
 
Дело в том что нет подходящего места чтобы это воткнуть. Попробуем здесь
Код
C++ (Qt)
void DrawEngine3D::Draw( void )
{
       ...
Image i1 = ResMan::loadImage( "image1" );
       ...
}
 
Машина знает что "image1" должен быть отрисован (она же его и рисует). Но затем она его просто грохнет - ведь больше ссылок (пока) нет. Следующая машина опять загрузит и опять грохнет. Также заметим: если картинка используется 2 и более раз (в течение Draw), то она не выгрузится, но счетчик ссылок на нее будет постоянно расти.

Код
C++ (Qt)
{
Image i1 = ResMan::loadImage( "image1" );
myImages.append( i1 );
}
 
{
Пользуемся картинками из коллекции myImages
 
А в каком месте создать эту коллекцию? Если не в методе Draw - придется повторить все действия машины чтобы определить "а рисуем ли мы этот имедж". И эти мучительные проверки надо выполнять при всех действиях пользователя (вкл/выкл текстуру, открыл/закрыл окно и др.). 


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 18, 2010, 12:34
А разве сейчас ты картинки грузишь прямо в методе draw?

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


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 18, 2010, 14:28
А разве сейчас ты картинки грузишь прямо в методе draw?
Да (строго говоря не я, а программер чью работу я продолжаю). И я с его решением согласен.

IMHO, вначале нужно подготовить все данные для рендеринга и потом использовать их.
Это можно делать в конструкторе движка или в специальном prepare-методе. Вот там и нужно подготовить коллекцию ресурсов, необходимых для отрисовки, а в Draw их только использовать.
С "движком" как раз все ясно - сначала все зарядил, потом рендер. Но то что мы обсуждаем - совсем не движок, а "редактор" где пользователь готовит все для рендера (подбирает модели, текстуры, анимацию и.т.п). Здесь нет возможности все "перезарядить с нуля" - нужно динамически реагировать на активность пользователя.


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 18, 2010, 14:39
Но то что мы обсуждаем - совсем не движок, а "редактор" где пользователь готовит все для рендера (подбирает модели, текстуры, анимацию и.т.п). Здесь нет возможности все "перезарядить с нуля" - нужно динамически реагировать на активность пользователя.
Ну так значит эту коллекцию ресурсов нужно держать в классе редактора.
Пользователь ткнул меню - загрузили картинку, положили в коллекцию, она осталась в кэше.
Ткнул в другом окне загрузить эту картинку, посмотрели - она есть в кэше, положили в коллекцию.
В двух коллекциях находятся только указатели на сами данные картинки.
Дальше пользователь ткнул выгрузить картинку из первого окна, она удалилась из его коллекции, но осталась в памяти для второго окна. Ткнул пользователь выгрузку этой картинки и из второго окна, она удалилась из коллекции и т.к. ее использовало только это окно - выгрузятся сами данные с вычеркиванием из кеша менеджера ресурсов.


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 18, 2010, 15:12
Ну так значит эту коллекцию ресурсов нужно держать в классе редактора.
Пользователь ткнул меню - загрузили картинку, положили в коллекцию, она осталась в кэше.
Ткнул в другом окне загрузить эту картинку, посмотрели - она есть в кэше, положили в коллекцию.
В двух коллекциях находятся только указатели на сами данные картинки.
Дальше пользователь ткнул выгрузить картинку из первого окна, она удалилась из его коллекции, но осталась в памяти для второго окна. Ткнул пользователь выгрузку этой картинки и из второго окна, она удалилась из коллекции и т.к. ее использовало только это окно - выгрузятся сами данные с вычеркиванием из кеша менеджера ресурсов.
То да, но как отслеживать все многочисленные изменения? Напр.

Дальше пользователь ткнул выгрузить картинку из первого окна,
Он не может так ткнуть. Вот проект пользователя который я использую для тестирования: пирамида (212) банок Pepsi-Cola. Все банки имеют одинаковую текстуру (наклейка Pepsi). Но в любой момент он может сменить текстуру на любой из банок. Или только на тех банках что он выбрал. Или на всех сразу (используя "мастер" текстуры). Или вообще выкинуть все/часть банок. А все открытые окна должны отобразить его действия.


Название: Re: Пример проектирования
Отправлено: ufna от Декабрь 18, 2010, 15:22
Игорь, а как у Вас храниться текстура для модели? Обычной практикой является использование ссылку на объект, за жизнь которого отвечает менеджер ресурсов. И запрос "загрузи мне текстуру" отдает ему грубо говоря указатель, а не копию.


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 18, 2010, 15:35
Он не может так ткнуть. Вот проект пользователя который я использую для тестирования: пирамида (212) банок Pepsi-Cola. Все банки имеют одинаковую текстуру (наклейка Pepsi). Но в любой момент он может сменить текстуру на любой из банок. Или только на тех банках что он выбрал. Или на всех сразу (используя "мастер" текстуры). Или вообще выкинуть все/часть банок. А все открытые окна должны отобразить его действия.
Соответственно, используем model/view.
В каждой модели есть менеджер текстур (все тоже о чем я и писал). 212 банок используют 1 текстуру. В кеше менеджера находится одна ссылка, текстура в памяти. Счетчик использования этой текстуры 212. Пользователь изменил текстуру на одной банке. Обратились в кэш, там ее еще нет, сгенерировали ее в памяти, поместили в кэш, назначили ее объекту "банке". shared_ptr на текстуру, которая была установлена для этой конкретной банки заменяется на новую. Соответственно, счетчик использования первой текстуры уменьшается на 1 и становится == 211, счетчик использования второй текстуры == 1.
Код
C++ (Qt)
typedef std::tr1::shared_ptr<Texture> TexturePtr;
class Banka ...
{
Banka()
{
m_tex = TexMan::load( "pepsi" );
}
 
void changeTex()
{
m_tex = TexMan::load( "coca" );
}
 
private:
TexturePtr m_tex;
};
 

Разрушили несколько объектов банок, разрушаться их объекты m_tex, уменьшив счетчик использования и при достижения 0 - данные о текстуре тоже разрушаться.


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 18, 2010, 15:47
Игорь, а как у Вас храниться текстура для модели? Обычной практикой является использование ссылку на объект, за жизнь которого отвечает менеджер ресурсов. И запрос "загрузи мне текстуру" отдает ему грубо говоря указатель, а не копию.
Ну вот так не надо  :-[. Это все равно что я басиста спросил "а ты можешь сыграть "The Lemon Song" из второго альбома?". Разумеется менеджер есть, обсуждается как его довести до ума 


Название: Re: Пример проектирования
Отправлено: ufna от Декабрь 18, 2010, 15:51
Ответ - могу ))

Просто по просмотру темы я совершенно не увидел как функционирует текущий менеджер ресурсов. Дали бы хоты бы хедеры для названных классов и в особенности менеджера.

Загрузка ресурсов в draw() - это конечно "О_о". И как раз стоит отталкиваться от того, что происходит у Вас, когда пользователь меняет текстуру. Что делается на данный момент?



Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 18, 2010, 17:21
В каждой модели есть менеджер текстур (все тоже о чем я и писал). 212 банок используют 1 текстуру. В кеше менеджера находится одна ссылка, текстура в памяти. Счетчик использования этой текстуры 212. Пользователь изменил текстуру на одной банке. Обратились в кэш, там ее еще нет, сгенерировали ее в памяти, поместили в кэш, назначили ее объекту "банке". shared_ptr на текстуру, которая была установлена для этой конкретной банки заменяется на новую. Соответственно, счетчик использования первой текстуры уменьшается на 1 и становится == 211, счетчик использования второй текстуры == 1.
Это именно то что нужно. Но как этого достичь? Где/как обновлять счетчик(и)? Мое мнение надо регистрировать текстуру (именно текстуру, а не картинку) для каждой машины. Что Вы об этом думаете? 

Загрузка ресурсов в draw() - это конечно "О_о". И как раз стоит отталкиваться от того, что происходит у Вас, когда пользователь меняет текстуру. Что делается на данный момент?
Ну в ситуации где собачья будка больше всего "движка" - нужно искать др. решения, цепляться за традиционный подход бесперспективно


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 18, 2010, 18:30
Моральная поддержка (чтоб легче думалось) - вот банки  :)


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 19, 2010, 15:25
Это именно то что нужно. Но как этого достичь? Где/как обновлять счетчик(и)?
Учет счетчиков и удаление неиспользуемых объектов обеспечивает shared-указатели, а контроль единичной загрузки данных обеспечивает кэш.
Мой пример, который я выкладывал выше, это демонстрирует

Мое мнение надо регистрировать текстуру (именно текстуру, а не картинку) для каждой машины.
IMHO, лучше хранить и картинки и текстуры.
Каждая текстура должна содержит в себе shared-указатель на картинку, из которой она сгенерирована.
Каждый объект сцены должен содержать в себе shared-указатель (или указатели) на текстуры.


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 19, 2010, 15:37
Учет счетчиков и удаление неиспользуемых объектов обеспечивает shared-указатели, а контроль единичной загрузки данных обеспечивает кэш.
Мой пример, который я выкладывал выше, это демонстрирует
Если Вы имеете ввиду этот пример
Код:
class Banka ...
{
Banka()
{
m_tex = TexMan::load( "pepsi" );
}
то нет, не обеспечивает. Нет никаких оснований загружать ресурс если неизвестно будет ли он использован. Будем думать или догматуху гнать?  :)


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 19, 2010, 15:42
то нет, не обеспечивает. Нет никаких оснований загружать ресурс если неизвестно будет ли он использован. Будем думать или догматуху гнать?  :)
Почему он не будет использоваться?  :)
Мы создаем объект сцены "Банка", на которой есть текстура "Пепси колы". Что здесь не известно, будет ли сцене объект банка или что на банке будет текстура?


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 19, 2010, 15:58
Почему он не будет использоваться?  :)
Мы создаем объект сцены "Банка", на которой есть текстура "Пепси колы". Что здесь не известно, будет ли сцене объект банка или что на банке будет текстура?
Неизвсестны все подробности машин(ы). Напр. может вообще не быть машин которые показывают этот 3D объект. Или они есть но переключены в режим(ы) где текстуры не нужны (напр wireframe и др) . Взаимодействие классов - самое сложное (я так думаю  :))


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 19, 2010, 17:08
Неизвсестны все подробности машин(ы). Напр. может вообще не быть машин которые показывают этот 3D объект. Или они есть но переключены в режим(ы) где текстуры не нужны (напр wireframe и др) . Взаимодействие классов - самое сложное (я так думаю  :))
Ну если никто этот объект не показывает, то он и создан не будет и соответственно текстура тоже не загрузиться.
Движок работает с объектом сцены, сцена это коллекция объектов, каждый объект содержит в качестве одного из параметров shared-указатель на текстуру. Если там 0, то и текстуры для этого объекта не загружено.
Пользователь может меняет количество объектов на сцена и свойства этих объектов. Захотел он изменить текстуру, спросили у менеджера ресурсов, он вернет на нее ссылку, при необходимости загрузив, присвоили ее переменной. Захотел убрать текстуру - делаем reset() shared-указателю, если текстура больше никому не нужна, она выгрузится.
В чем проблема?  ::)




Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 19, 2010, 17:15
Первое что приходит в голову - зарядить такой метод

Код
C++ (Qt)
bool Engine3D::IsTextureUsed( const Texture * ) const;
 

Ну или так

Код
C++ (Qt)
bool Engine3D::IsImageUsed( const Image * ) const;
 

Но так не катит. Машина не владеет ни объектами, ни их текстурами - а значит и конечными ресурсами. Машина просто рисует то что ей дали (учитывая свои опции установленные пользователем). Реально метод Draw выглядит так

Код
C++ (Qt)
void Engine3D::Draw( container <Object3D *> & iObject );
 


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 19, 2010, 17:31
Ну если никто этот объект не показывает, то он и создан не будет и соответственно текстура тоже не загрузиться
Ну привет, объекты (Object3D) "первичны", они существуют всегда. Просто что и как отображать - дело пользователя.

текстура тоже не загрузиться
Текстура "что сделает?" - значит здесь мягкого знака нет. А вот напр. "почему бы текстуре не загрузиться?" - текстуре "что сделать?", значит мягкий знак есть (хотя слово то же самое). Пожалуйста не сочтите за нравоучение (как Вы говорите "без обид"). Просто не у всех мама была учителем русского языка  :)


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 19, 2010, 19:24
Ну привет, объекты (Object3D) "первичны", они существуют всегда. Просто что и как отображать - дело пользователя.
И что?  :)
На сцене есть 212 объектов (банок), у каждого объекта есть поле текстуры. Если для этого объекта она установлена, значит эта текстура загружена в память. А при рендеринге, если пользователь выбрал режим без наложения текстур, эти текстуры не накладываются.

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

Это все можно посмотреть в том коде, который я приводил выше.


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 19, 2010, 20:09
Если для этого объекта она установлена, значит эта текстура загружена в память.
Ну так было бы жить легко и приятно. Но увы - не получается. Object3D может иметь текстуру, но совсем необязательно что она роялит - это зависит и от др. классов тоже (машин). Не расстраивайтесь, это (долбаное взаимодействие) реально сложно  :)


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 19, 2010, 20:11
Ну так было бы жить легко и приятно. Но увы - не получается. Object3D может иметь текстуру, но совсем необязательно что она роялит - это зависит и от др. классов тоже (машин).
Какое отношение имеет загрузка/выгрузка ресурсов к "рояленью" текстуры для отрисовки?

Не расстраивайтесь, это (долбаное взаимодействие) реально сложно  :)
А я и не расстраиваюсь.  ;D
Сложным это взаимодействие делает разработчик. Спроектировал фигню - получай головную  боль. Так и должно быть, все нормально, привыкай.  ;)


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 19, 2010, 20:51
Какое отношение имеет загрузка/выгрузка ресурсов к "рояленью" текстуры для отрисовки?
Да то самое: если ресурс не роялит (никому не нужен) - зачем его грузить?

Сложным это взаимодействие делает разработчик. Спроектировал фигню - получай головную  боль.
Кто ж спорит? Давайте спроектируем "не фигню".Что Вы предлагаете? Грузить "всех подряд" - ну так это уже было "до меня" (детали не важны), заказчика это не устраивает (по памяти и скорости), вот  и он обратился ко мне.

Так и должно быть, все нормально, привыкай.  ;)
Чем раньше Вы оставите менторский тон - тем лучше, и в первую очередь для Вас. То Вы для студенческих лаб будете демонстрировать виртуозную технику, а здесь надо напрягаться и выкладываться. Умеете ли? Или вся силенка ушла в дуст?  :)


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 19, 2010, 21:29
Да то самое: если ресурс не роялит (никому не нужен) - зачем его грузить?
Ну это выясняется только сейчас, да и то без конкретики. Извиняюсь, не протелепатировал.  :)

заказчика это не устраивает (по памяти и скорости), вот  и он обратился ко мне.
А ты за решением обратился на форум и даже не потрудился сформулировать вопрос полностью.

Чем раньше Вы оставите менторский тон - тем лучше, и в первую очередь для Вас.
Тоже могу пожелать и тебе.

То Вы для студенческих лаб будете демонстрировать виртуозную технику
Ой, я бы с удовольствием делал.  :)

а здесь надо напрягаться и выкладываться. Умеете ли? Или вся силенка ушла в дуст?  :)
Точно. Напрягаться и выкладываться, а в первую очередь думать, а не искать решения на форумах.
Я изложил свою идею, теперь ты изложи свою. И посмотрим.

Умеете ли?
Кое что умею.  ;)


Название: Re: Пример проектирования
Отправлено: ufna от Декабрь 20, 2010, 00:06
Irogs, Вы смешной :)) Описанная проблема - это просто азы в области, в которой Вы еще пытаетесь кого-то учить. На самом деле забавляет.

Задача должна решаться комплексно. Лень разбираться в том как у Вас работаю классы и какие косяки.

Загрузка ресурса должна происходить в тот момент, когда модели назначается этот ресурс. Выгрузка (и/или проверка на "удаление") - когда модель удаляется или ей меняется текстура. Вопрос - где сложность ???

Если у Вас кастрированный менеджер ресурсов - пишите нормальный. Я еще раз спрашиваю - как у Вас происходит назначение текстуры для модели? Редактор, ок, хорошо, но есть момент когда происходит смена. Вот именно в тот самый момент из тех самых моментов должны происходить все действия менеджера ресурсов. Но ни в коем случае не в draw и т.п.

Вам дали уже несколько путей решения. И это - азы, это - основа любых 3д движков претендующих на что-то большее, чем демка для QtDemo.


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 20, 2010, 18:07
как у Вас происходит назначение текстуры для модели? Редактор, ок, хорошо, но есть момент когда происходит смена. Вот именно в тот самый момент из тех самых моментов должны происходить все действия менеджера ресурсов. Но ни в коем случае не в draw и т.п.
Максимально упрощенно: пользователь выбрал модель. Открыл стандартное окно "файл" и выбрал картинку. Создался объект Texture, который принадлежит Object3D. Этот объект Texture замещает имеющийся.

Простой способ: вот когда текстура назначена, тогда и загрузим ее картинку. Понятно картинки кэшируются и  одна и та же не грузится дважды. Это по существу так как сделано сейчас (неважно из какого метода). Работает плохо: часто память забита картинками которые нигде не отображаются. Паузы на загрузке проекта - тоже загрузка многочисленных ненужных имеджей. Задача иметь в памяти только те что отображаются хотя бы в 1 открытом окне.

Irogs, Вы смешной :))
Сначала разберитесь, а там видно будет кто смешной  :)

2BRE: не злитесь  :)


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 20, 2010, 18:25
Простой способ: вот когда текстура назначена, тогда и загрузим ее картинку. Понятно картинки кэшируются и  одна и та же не грузится дважды. Это по существу так как сделано сейчас (неважно из какого метода). Работает плохо: часто память забита картинками которые нигде не отображаются. Паузы на загрузке проекта - тоже загрузка многочисленных ненужных имеджей. Задача иметь в памяти только те что отображаются хотя бы в 1 открытом окне.
Забавно. Ты описал то, что делает вышеприведенный пример.  :)


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 20, 2010, 18:34
Забавно. Ты описал то, что делает вышеприведенный пример.  :)
Я уже раза 3 отвечал что нет  :) Ведь Вы сказали загрузить имедж не сделав никаких проверок что он в использовании. А если даже предположить что Ваш load не грузит (пока) имеджа - не видно как Вы управляете счетчиком ссылок.


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 20, 2010, 18:42
Я уже раза 3 отвечал что нет  :)
Я вижу только одно объяснение - ты с ним не разобрался.

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

не видно как Вы управляете счетчиком ссылок.
Это не видно, потому что ты не знаешь как работает shared_ptr. Скажу по секрету - они спрятаны у него внутри.  ;)


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 20, 2010, 19:04
Картинка будет грузиться в тот момент, когда в этом будет необходимость разработчику.
Покажи, какие нужны проверки и где именно, а уж их добавить не проблема. 
Картинка нужна/используется когда хотя бы одна из машин рисует текстуру на объекте. Поэтому мне кажется что и грузить (или брать из кэша) ее лучше там же, в Engine3D::Draw. Нет надежного способа предвычислить "а какие картинки будут использоваться", как я писал выше.


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 20, 2010, 19:19
Картинка нужна/используется когда хотя бы одна из машин рисует текстуру на объекте. Поэтому мне кажется что и грузить (или брать из кэша) ее лучше там же, в Engine3D::Draw. Нет надежного способа предвычислить "а какие картинки будут использоваться", как я писал выше.
Не знаю насколько это будет эффективно загружать картинки при рисовании...

Например, в каждом объекте сцена можно добавить переменную типа TextureInfo.
В ней сохраняются все данные для генерации текстуры (имя файла картинки, размер, ...), сами данные не подгружается.
При начале рисования, в методе Draw, пробегаемся по всем объектам сцены и определяем нужно ли генерировать текстуру для этого объекта, если да, то используя данные из TextureInfo загружаем файл и генерируем текстуру.


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 20, 2010, 19:40
Не знаю насколько это будет эффективно загружать картинки при рисовании...
Нормально, Draw рисует в offscreen буфер

Например, в каждом объекте сцена можно добавить переменную типа TextureInfo.
В ней сохраняются все данные для генерации текстуры (имя файла картинки, размер, ...), сами данные не подгружается.
При начале рисования, в методе Draw, пробегаемся по всем объектам сцены и определяем нужно ли генерировать текстуру для этого объекта, если да, то используя данные из TextureInfo загружаем файл и генерируем текстуру.
Большие проблемы с "пробежкой". Машины самые разнообразные, могут содержать самые разные наборы объектов (которые им просто даются для рисования). Не выходит поставить какое-то управление в начало Draw - это всего одна машина, есть и другие. Поэтому надо крутить со счетчиком ссылок записанным, конечно, в самом ресурсе, но обновляемым по-умному.

Могу рассказать как я сделал (если не будете на меня кричать, обвинять в неграмотности и.т.п  :))


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 20, 2010, 19:44
Могу рассказать как я сделал (если не будете на меня кричать, обвинять в неграмотности и.т.п  :))
С удовольствием посмотрю.
А свое субъективное мнение озвучить можно будет?  :) Может что-то полезное ляпну...  ::)


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 21, 2010, 11:42
Ясно что Texture должна использовать shared Image, но не загружать их без необходимости. Загрузка (и увеличение счетчика ссылок на картинку) выполняется в момент "подключения" текстуры (а не самой картинки) к машине.

Код
C++ (Qt)
void DrawEngine3D::Draw( void )
{
 ...
 Texture * txtr = it->GetTexture();   // есть текстура, ее надо рисовать
 ...
 if (!this->TextureAdded(txtr))       // машине уже известна такая текстура ?
   this->AddTexture(txtr);             // если нет, регистрируем текстуру
 
 Image * img = txtr->Image();    // получили картинку для рисования БЕЗ увеличения счетчика ссылок на нее
...  
// используем картинку
}
 
bool DrawEngine3D::AddTexture( Texture * txtr )
{
 if (mTextures.find(txtr) != mTextures.end()) return false;  // такая уже есть
 mTextures.insert(txtr);                     // запомнили
 txtr->AddToEngine(this);                   // говорим текстуре "подключиться" к машине
 return true;
}
 
bool Texture::AddToEngine( DrawEngine3D * engine )
{
 if (mEngines.find(engine) != mEngines.end()) return false;  // такая уже есть ?
 mEngines.insert(engine);                               // запомнили
 this->LoadImage();                                        // загружаем имедж, +1 к счетчику
 return true;
}
 


"Отключение" текстуры от машины аналогично, просто действия обратные. Деструктор DrawEngine3D зовет все (известные ему) текстуры чтобы они уменьшили свои счетчики на картинки. Особо расписывать здесь нечего, может стоит только упомянуть

Код
C++ (Qt)
Texture::~Texture( void )
{
 ..
 RemoveFromAllEngines();  
// теперь текстура отключена от всех машин, счетчик ссылок на картинку Ok
 ..
}
 
void Texture::RemoveFromAllEngines( void )
{
 typedef std::set <DrawEngine3D *> TEngineSet;
 TEngineSet temp = mEngines; // нужна копия
 
 for (TEngineSet::iterator it = temp.begin(); it != temp.end(); ++it)
  (*it)->RemoveTexture(this);
 
 mEngines.clear();
}
 


Название: Re: Пример проектирования
Отправлено: BRE от Декабрь 21, 2010, 13:45
Я правильно понял, что метод DrawEngine3D::Draw может вызываться несколько раз и при каждом вызове он может загружать текстуру?
То есть:
Код
C++ (Qt)
DrawEngine3D eng( ... );
...
eng.draw(); // Загрузили одну текстуру
...
eng.draw(); // а здесь другую
 

А отключение текстур происходит только при разрушении объекта eng?


Название: Re: Пример проектирования
Отправлено: Igors от Декабрь 21, 2010, 14:37
Я правильно понял, что метод DrawEngine3D::Draw может вызываться несколько раз и при каждом вызове он может загружать текстуру?
То есть:
Код
C++ (Qt)
DrawEngine3D eng( ... );
...
eng.draw(); // Загрузили одну текстуру
...
eng.draw(); // а здесь другую
 
Да, напр если появилась новая текстура между 2-мя вызовами draw

А отключение текстур происходит только при разрушении объекта eng?
Не только, при удалении текстуры тоже. Также текстура может перегрузить имедж сама. Напр. пользователь не менял объект Texture, а подобрал для нее др. картинку. Тогда надо просто вызвать Texture::RemoveFromAllEngines()