[flyweight] key value with objects not constructible from a key

The flyweight factory concept seems to be missing a key part of the GoF Flyweight pattern's FlyweightFactory participant. The GoF factory supplies the flyweight if one exists. This is what the current boost flyweight Factory concept does. If no flyweight exists the GoF flyweight factory creates one. In the boost flyweight library this functionality is somewhere else that doesn't seem to be described in the documentation. The name factory for what is currently in place is misleading. Isn't it really just an associative container? I'm trying to use flyweight to represent texture objects right now. These are surely a good candidate for use with flyweight as they are one of the examples given. My textures are fairly simple objects, with low coupling. Creating a texture is however expensive and has a lot of dependencies (libjpeg,libpng,etc). I typically have a TextureFactory which decouples the construction of a texture from the actual texture object and allows the work required to setup the various libraries to read images to be reused by all textures constructed from the same TextureFactory instance. It doesn't seem possible to have this decoupling with flyweight. Am I missing something? -- Michael Marcin

Michael Marcin wrote:
The name factory for what is currently in place is misleading. Isn't it really just an associative container?
The name factory is only used to refer to the internal mechanism Boost.Flyweight uses to store all elements. Boost.Flyweight is neither a factory nor a a container, it is a smart reference (in lack of a better name). flyweight<T> aims at being the same at T, except it will use sharing behind the scenes to reduce memory usage (each flyweight<T> is actually a reference to some T stored in a flyweight factory shared between all instances). Also, you're saying it's not a factory but really an associative container, while a factory is defined as being an associative container in Boost.Flyweight.
I'm trying to use flyweight to represent texture objects right now. These are surely a good candidate for use with flyweight as they are one of the examples given.
And the example really tells everything about how it should be done.
My textures are fairly simple objects, with low coupling. Creating a texture is however expensive and has a lot of dependencies (libjpeg,libpng,etc). I typically have a TextureFactory which decouples the construction of a texture from the actual texture object and allows the work required to setup the various libraries to read images to be reused by all textures constructed from the same TextureFactory instance.
It doesn't seem possible to have this decoupling with flyweight. Am I missing something?
The thing is you're not create a factory or perform decoupling at all. The goal of boost::flyweight is to do that for you. So you just write struct texture { texture(const std::string& name) { /* load up my texture */ } ... }; And then you have a texture, which can be constructed independently of any other instance without any factory or anything, which doesn't share state with anyone etc. Basically, a fairly normal value type with eager evaluation, with the advantages and disadvantages it means. And that's when flyweight kicks in. It will turn any type into a memoized one, using a global table behind the scenes. Boost.Flyweight does this by taking an object of such a type, seeing if it is in a global table, and behaving as a reference to that object in the table. If the object is not in the table, it is added. The problem, however, is that this approach requires the object to be constructed *before* the look-up, which is potentially useless if it's already in the table. The basic idea is then to be able to provide the constructor arguments instead of the object itself, and use those arguments as key for the look-up or for creating the object if needed. That is what Boost.Flyweight does under the key_value name. flyweight<T> could be implemented in terms of flyweight<key_value<T, T, self_extractor> >. If there is some specific thing you want to customize, changing the factory, holder, locking ou tracking policy to custom ones is also possible. The factory defines the associative container being used. The holder defines how the factory is stored. Locking policy and tracking policies seem self-explanatory. You can even do garbage collection by using no tracking and doing the work in the factory. May not be a very elegant design however.

Michael Marcin escribió:
The flyweight factory concept seems to be missing a key part of the GoF Flyweight pattern's FlyweightFactory participant. The GoF factory supplies the flyweight if one exists. This is what the current boost flyweight Factory concept does. If no flyweight exists the GoF flyweight factory creates one. In the boost flyweight library this functionality is somewhere else that doesn't seem to be described in the documentation.
The name factory for what is currently in place is misleading. Isn't it really just an associative container?
Well, this terminology issue has been raised before and I finally decided to stick with "factory" for lack of a more descriptive name. It is not an associative container, though in most cases factories are built on top of associative containers. I hope the important thing is that the reader gets a clear understanding of what these factories do in the context of Boost.Flyweight.
I'm trying to use flyweight to represent texture objects right now. These are surely a good candidate for use with flyweight as they are one of the examples given. My textures are fairly simple objects, with low coupling. Creating a texture is however expensive and has a lot of dependencies (libjpeg,libpng,etc). I typically have a TextureFactory which decouples the construction of a texture from the actual texture object and allows the work required to setup the various libraries to read images to be reused by all textures constructed from the same TextureFactory instance.
It doesn't seem possible to have this decoupling with flyweight. Am I missing something?
Not sure if this is what you're after, but have you consulted the section on key-value flyweights? http://svn.boost.org/svn/boost/sandbox/flyweight/libs/flyweight/doc/tutorial... Does this approach your use case? Joaquín M López Muñoz Telefónica, Investigación y Desarrollo

joaquin@tid.es wrote:
Michael Marcin escribió:
Well, this terminology issue has been raised before and I finally decided to stick with "factory" for lack of a more descriptive name. It is not an associative container, though in most cases factories are built on top of associative containers. I hope the important thing is that the reader gets a clear understanding of what these factories do in the context of Boost.Flyweight.
I think the name is fine if the concept is extended to construct the values as well as store them.
Not sure if this is what you're after, but have you consulted the section on key-value flyweights?
Yes
Does this approach your use case?
Not quite. Let's say my key is a filename and my value is a texture. If texture has to be constructible from key then texture has to have direct access to file and image loading functionality. I can't pass any information other than the key to the construction site without resorting to singletons. If factory handled the construction of texture I could have a custom factory which I passed a log, a filesystem, pluggable factory methods for handing additional image formats, etc. I believe it also would allow for polymorphic flyweights. The factory could construct a derived type from the key and store reference to the base type. -- Michael Marcin

Michael Marcin <mike.marcin <at> gmail.com> writes:
joaquin <at> tid.es wrote:
Michael Marcin escribió:
[...]
Let's say my key is a filename and my value is a texture.
If texture has to be constructible from key then texture has to have direct access to file and image loading functionality. I can't pass any information other than the key to the construction site without resorting to singletons.
OK, please help me make out a more complete picture of the use case you've got in mind. Imagine you have a flyweight type that behaves the way you need/want, let's call it texture_flyweight. The associated key is std::string and the associated value is texture. Now, you write the following: texture_flyweight fw("wood.texture"); If an equivalent flyweight already exists then there is no problem, fw will be assigned the same texture object, this is how Boost.Flyweight currently works. Now, imagine that no equivalent texture exists, and yet you can't construct a texture object from "wood.texture" alone. How would you have then the above statement behave? Joaquín M López Muñoz Telefónica, Investigación y Desarrollo

On Wed, Nov 12, 2008 at 16:09, Joaquin M Lopez Munoz <joaquin@tid.es> wrote:
If an equivalent flyweight already exists then there is no problem, fw will be assigned the same texture object, this is how Boost.Flyweight currently works. Now, imagine that no equivalent texture exists, and yet you can't construct a texture object from "wood.texture" alone. How would you have then the above statement behave?
Perhaps there is no constructor for a texture that takes the key, but instead there's some load_texture function that can construct a texture from the key? I didn't notice an easy way to include the opposite of an extractor that can go from key to value, so it seems like doing that would require wrapping a factory.

Scott McMurray <me22.ca+boost <at> gmail.com> writes:
On Wed, Nov 12, 2008 at 16:09, Joaquin M Lopez Munoz <joaquin <at> tid.es> wrote:
If an equivalent flyweight already exists then there is no problem, fw will be assigned the same texture object, this is how Boost.Flyweight currently works. Now, imagine that no equivalent texture exists, and yet you can't construct a texture object from "wood.texture" alone. How would you have then the above statement behave?
Perhaps there is no constructor for a texture that takes the key, but instead there's some load_texture function that can construct a texture from the key?
Then you can do something like this: struct texture { ... }; texture load_from_texture(const std::string) { ... } struct loaded_texture:texture { loaded_texture(const std::string& str): texture(load_from_texture(str)) {} }; typedef flyweight<key_value<std::string,loaded_texture> > texture_flyweight; Note that loaded_texture can be treated as an implementation artifact: texture_flyweight is convertible to texture, which is the type you're interested about. Joaquín M López Muñoz Telefónica, Investigación y Desarrollo

Joaquin M Lopez Munoz wrote:
Scott McMurray <me22.ca+boost <at> gmail.com> writes:
On Wed, Nov 12, 2008 at 16:09, Joaquin M Lopez Munoz <joaquin <at> tid.es> wrote:
If an equivalent flyweight already exists then there is no problem, fw will be assigned the same texture object, this is how Boost.Flyweight currently works. Now, imagine that no equivalent texture exists, and yet you can't construct a texture object from "wood.texture" alone. How would you have then the above statement behave?
Perhaps there is no constructor for a texture that takes the key, but instead there's some load_texture function that can construct a texture from the key?
Then you can do something like this:
struct texture { ... };
texture load_from_texture(const std::string) { ... }
struct loaded_texture:texture { loaded_texture(const std::string& str): texture(load_from_texture(str)) {} };
typedef flyweight<key_value<std::string,loaded_texture> > texture_flyweight;
Note that loaded_texture can be treated as an implementation artifact: texture_flyweight is convertible to texture, which is the type you're interested about.
But loaded_texture's can't share any state or be initialized with state other than the key. class TextureFactory { public: TextureFactory( Log& log ) : m_log( log ) {} GLuint LoadImage( const std::string& filename ) { if( ExtensionMatches(filename,".png") ) { m_log << "Loading png: " << filename; return m_pngLoader.Load( filename ); } else { throw UnsupportedImageFormat( filename ); } } // rest of factory implementation goes here private: Log& m_log; PngLoader m_pngLoader; // heavy object to construct }; -- Michael Marcin

On Wed, Nov 12, 2008 at 17:11, Michael Marcin <mike.marcin@gmail.com> wrote:
But loaded_texture's can't share any state or be initialized with state other than the key.
But if you load a value one way, I'm not convinced that getting a version that happens to have the same key but was loaded differently is something that should be encouraged. Why not just make your key more complex, since you need to pass all the information needed to construct one when you retrieve one anyways?

Scott McMurray <me22.ca+boost <at> gmail.com> writes:
On Wed, Nov 12, 2008 at 17:11, Michael Marcin <mike.marcin <at> gmail.com> wrote:
But loaded_texture's can't share any state or be initialized with state other than the key.
But if you load a value one way, I'm not convinced that getting a version that happens to have the same key but was loaded differently is something that should be encouraged.
Why not just make your key more complex, since you need to pass all the information needed to construct one when you retrieve one anyways?
The following might be one possible approach to your envisioned scenario using the technique proposed by Scott. There is one significant difference with the way TextureFactory is defined, namely that construct is required to return a Texture (your version does the construction using placement new). Complete sample follows: ***BEGIN CODE*** #include <boost/assert.hpp> #include <boost/flyweight.hpp> #include <boost/flyweight/key_value.hpp> #include <string> #include <cassert> using namespace boost::flyweights; struct Texture { }; struct Log { }; struct TextureFactory { TextureFactory(const Log&){} Texture construct(const std::string& filename) { return Texture(); // real code would do something different } }; struct KeyAndFactory { KeyAndFactory(const std::string key,TextureFactory& factory): key(key),pfactory(&factory) {} std::string key; TextureFactory* pfactory; }; std::size_t hash_value(const KeyAndFactory& kf) { boost::hash<std::string> h; return h(kf.key); } bool operator==(const KeyAndFactory& kf1,const KeyAndFactory& kf2) { return kf1.key==kf2.key; } struct LoadedTexture:Texture { LoadedTexture(const KeyAndFactory& kf): Texture(kf.pfactory->construct(kf.key)) {} }; typedef flyweight<key_value<KeyAndFactory,LoadedTexture> > TextureFlyweight; int main() { Log log1,log2; TextureFactory factory1(log1),factory2(log2); TextureFlyweight fw1("hello",factory1); TextureFlyweight fw2("hello",factory2); TextureFlyweight fw3("bye",factory1); const Texture& t1=fw1; assert(fw1==fw2); assert(fw1!=fw3); } ***END CODE*** Is this more similar to what you have in mind? Joaquín M López Muñoz Telefónica, Investigación y Desarrollo

Joaquin M Lopez Munoz wrote:
The following might be one possible approach to your envisioned scenario using the technique proposed by Scott. There is one significant difference with the way TextureFactory is defined, namely that construct is required to return a Texture (your version does the construction using placement new).
I only chose the placement new because flyweight already has an allocator template parameter. If allocation became part of the factory then I think more cool option occur like polymorphic flyweights where a key constructs a derived type via the factory but the flyweight and calling code only knows about the base type.
Complete sample follows:
typedef flyweight<key_value<KeyAndFactory,LoadedTexture> > TextureFlyweight;
int main() { Log log1,log2; TextureFactory factory1(log1),factory2(log2);
TextureFlyweight fw1("hello",factory1); TextureFlyweight fw2("hello",factory2); TextureFlyweight fw3("bye",factory1); const Texture& t1=fw1;
assert(fw1==fw2); assert(fw1!=fw3); }
Is this more similar to what you have in mind?
That is certainly a lot closer. I didn't know that flyweights could have multiple construction parameters. I didn't see any examples of that in the docs, it seems like a pretty cool feature you might show off somewhere. -- Michael Marcin

Michael Marcin escribió:
Joaquin M Lopez Munoz wrote:
The following might be one possible approach to your envisioned scenario using the technique proposed by Scott. There is one significant difference with the way TextureFactory is defined, namely that construct is required to return a Texture (your version does the construction using placement new).
I only chose the placement new because flyweight already has an allocator template parameter. If allocation became part of the factory then I think more cool option occur like polymorphic flyweights where a key constructs a derived type via the factory but the flyweight and calling code only knows about the base type.
I don't see how the use of allocators hint at the possibility to have polymorphic flyweights. flyweight<T> treats T as values --they are stored in a factory typically implemented with an associative container, and STL containers are not polymorphic. In order to have polymorphism you'd have to use something like flyweight<T*>.
Is this more similar to what you have in mind
That is certainly a lot closer.
I'm glad about it. This kind of techniques to bring external value factories into play were discussed in conversations with Alberto Barbati: http://lists.boost.org/Archives/boost/2008/09/142227.php where it was also pointed out that a future extension of key-value flyweights might accept additional "factory functors" to make this type of constructs explicit and thus avoid such admittedly contrived tricks. I don't plan to have this in the initial version of the library so as to be able to gain more real-life feedback, I hope in the meantime the techniques just described serve your purposes. Regarding the terminology issue with what "factory" really means within B.F, I think it'll help to clarify things to distinguish these two entities: * A *flyweight* factory is the entity in charge of producing flyweight objects on demand. This is what's called a factory in B.F. * A *value* factory is the entity that produces values from keys and other conextual information, like your TextureFactory. This concept is not currently explicit in B.f, though it can be more or less acommodated as shown previously and might receive official treatment in future releases of the lib via "factory functors".
I didn't know that flyweights could have multiple construction parameters. I didn't see any examples of that in the docs, it seems like a pretty cool feature you might show off somewhere.
It is documented in the reference and mentioned at a comment in one of the snippets of the tutorial: // flyweight<T> can be constructed in the same way as T objects can, // even with multiple argument constructors I might try to give it more prominence in the tutorial, though. Joaquín M López Muñoz Telefónica, Investigación y Desarrollo

Michael Marcin <mike.marcin <at> gmail.com> writes:
Joaquin M Lopez Munoz wrote:
Then you can do something like this:
struct texture { ... };
texture load_from_texture(const std::string) { ... }
struct loaded_texture:texture { loaded_texture(const std::string& str): texture(load_from_texture(str)) {} };
typedef flyweight<key_value<std::string,loaded_texture> > texture_flyweight;
Note that loaded_texture can be treated as an implementation artifact: texture_flyweight is convertible to texture, which is the type you're interested about.
But loaded_texture's can't share any state or be initialized with state other than the key.
[...] OK, then we're back to my original question: how do yo expect the following statement to behave texture_flyweight fw("wood.texture"); when no previous equivalent flyweight object exists? Please note that I'm not fooling around your problem, I understand it but we have the crucial point that *all* the information necessary to construct the value should be available at the point of flyweight construction. This is not AFAICS a particular limitation of Boost.Flyweight, but just the way things are. This is why I'm inviting you to come up with a suitable creation process (regardless of whether it is implementable with B.F or not) so that I can see how you expect the value construction information to be provided. Just sketch how your ideal flyweight library should behave under these circumstances and we'll see whether B.F is up to the task or not. Joaquín M López Muñoz Telefónica, Investigación y Desarrollo

Joaquin M Lopez Munoz wrote:
Michael Marcin <mike.marcin <at> gmail.com> writes:
But loaded_texture's can't share any state or be initialized with state other than the key. [...]
OK, then we're back to my original question: how do yo expect the following statement to behave
texture_flyweight fw("wood.texture");
when no previous equivalent flyweight object exists? Please note that I'm not fooling around your problem, I understand it but we have the crucial point that *all* the information necessary to construct the value should be available at the point of flyweight construction. This is not AFAICS a particular limitation of Boost.Flyweight, but just the way things are. This is why I'm inviting you to come up with a suitable creation process (regardless of whether it is implementable with B.F or not) so that I can see how you expect the value construction information to be provided. Just sketch how your ideal flyweight library should behave under these circumstances and we'll see whether B.F is up to the task or not.
The information does exist, in the custom factory. I'm going to move closer to real code but still try to keep all of our project's nasty details out of the picture. int main() { // we use a lot of logging, its a complex project Log logger( "log.txt" ); // all the game's assets are actually stored in an archive // direct file access is not enough to load images. ZipArchive zip( "game.zip" ); typedef std::auto_ptr<TextureFactory> TextureFactoryPtr; TextureFactoryPtr textureFactory( new TextureFactory(zip,logger) ); // we don't want to have to recompile all code that uses textures // just to to add or remove support for an image format textureFactory->AddImageLoader( ".png", new PngLoader ); textureFactory->AddImageLoader( ".jpeg", new JpegLoader ); // convenient access function to custom holder which holds a pointer // to a TextureFactory get_holder<texture_flyweight>()->Initialize(textureFactory.get()); { // calls into TextureFactory to see if the object already exists // this is what flyweight's factory concept currently supports // TextureFactory models the current factory as well as providing // construction // // it doesnt exist so it calls into TextureFactory construct // which uses PngLoader to get a GLuint // it uses the GLuint and the filename (to support key extraction) // to construct a Texture texture_flyweight fw("wood.png"); } // holder's TextureFactory pointer set to null, not strictly // necessary but allows us to flag erroneous usage of // texture_flyweights during shutdown get_holder<texture_flyweight>()->Destroy(); // all texture_flyweights should be destroyed by this point, // TextureFactory's destructor can enforce this // with an assertion perhaps textureFactory.reset(); } -- Michael Marcin

Joaquin M Lopez Munoz wrote:
Michael Marcin <mike.marcin <at> gmail.com> writes:
joaquin <at> tid.es wrote:
Michael Marcin escribió: [...] Let's say my key is a filename and my value is a texture.
If texture has to be constructible from key then texture has to have direct access to file and image loading functionality. I can't pass any information other than the key to the construction site without resorting to singletons.
OK, please help me make out a more complete picture of the use case you've got in mind. Imagine you have a flyweight type that behaves the way you need/want, let's call it texture_flyweight. The associated key is std::string and the associated value is texture. Now, you write the following:
texture_flyweight fw("wood.texture");
If an equivalent flyweight already exists then there is no problem, fw will be assigned the same texture object, this is how Boost.Flyweight currently works. Now, imagine that no equivalent texture exists, and yet you can't construct a texture object from "wood.texture" alone. How would you have then the above statement behave?
It would go to the factory's construct method which might look like: void TextureFactory::construct( void* address, const std::string& filename ) { // complex work involving lots of 3rd party libraries // happens here GLuint glTextureName = LoadImage( filename ); // texture stays simple new(address) Texture( glTextureName, filename ); } -- Michael Marcin

Michael Marcin <mike.marcin <at> gmail.com> writes:
Joaquin M Lopez Munoz wrote:
OK, please help me make out a more complete picture of the use case you've got in mind. Imagine you have a flyweight type that behaves the way you need/want, let's call it texture_flyweight. The associated key is std::string and the associated value is texture. Now, you write the following:
texture_flyweight fw("wood.texture");
If an equivalent flyweight already exists then there is no problem, fw will be assigned the same texture object, this is how Boost.Flyweight currently works. Now, imagine that no equivalent texture exists, and yet you can't construct a texture object from "wood.texture" alone. How would you have then the above statement behave?
It would go to the factory's construct method which might look like:
void TextureFactory::construct( void* address, const std::string& filename ) { // complex work involving lots of 3rd party libraries // happens here GLuint glTextureName = LoadImage( filename );
// texture stays simple new(address) Texture( glTextureName, filename ); }
This can be covered in a manner similar to the one described in my reply to Scott's post: struct Texture { Texture(GLuint,const std::string&); ... }; struct LoadedTexture:Texture { LoadedTexture(const std::string& filename): Texture(LoadImage(filename),filename) {} }; typedef flyweight<key_value<std::string,LoadedTexture> > TextureFlyweight; Does this address your needs? Joaquín M López Muñoz Telefónica, Investigación y Desarrollo

Michael Marcin wrote:
It would go to the factory's construct method which might look like:
void TextureFactory::construct( void* address, const std::string& filename ) { // complex work involving lots of 3rd party libraries // happens here GLuint glTextureName = LoadImage( filename );
// texture stays simple new(address) Texture( glTextureName, filename ); }
Rather than doing this, it might be more elegant to return a factory function, such as in_place or functional/factory.

Michael Marcin wrote:
If texture has to be constructible from key then texture has to have direct access to file and image loading functionality. I can't pass any information other than the key to the construction site without resorting to singletons.
What's the problem with making your image loaders singletons? As far as I can think, they're the perfect candidates.

Mathias Gaunard wrote:
Michael Marcin wrote:
If texture has to be constructible from key then texture has to have direct access to file and image loading functionality. I can't pass any information other than the key to the construction site without resorting to singletons.
What's the problem with making your image loaders singletons? As far as I can think, they're the perfect candidates.
When loading a texture in OpenGL the texture is bound to the current OpenGL context which is stored per thread. OpenGL textures must be destroyed and recreated when you change resolution because you must also destroy and recreate your OpenGL context. A thread can have multiple contexts but only 1 may be current at time. The collection of textures, which flyweight stores in its factory, is logically bound to the current render context on the current thread. A singleton isn't really a good fit for a flyweight factory of OpenGL textures. As for the image loaders themselves for at least some of them I have to create multiple instances in order to use them concurrently. -- Michael Marcin

Michael Marcin wrote:
When loading a texture in OpenGL the texture is bound to the current OpenGL context which is stored per thread. OpenGL textures must be destroyed and recreated when you change resolution because you must also destroy and recreate your OpenGL context. A thread can have multiple contexts but only 1 may be current at time. The collection of textures, which flyweight stores in its factory, is logically bound to the current render context on the current thread.
So you need a per-context factory. Boost.Flyweight factories are global by design, the way I see them. So it's not ideal for what you want. You can get around this by putting the context in the key, however.
A singleton isn't really a good fit for a flyweight factory of OpenGL textures.
As for the image loaders themselves for at least some of them I have to create multiple instances in order to use them concurrently.
You could have a global concurrent queue and you could take one from it to load a file then put it back once done.

Mathias Gaunard wrote:
Michael Marcin wrote:
When loading a texture in OpenGL the texture is bound to the current OpenGL context which is stored per thread. OpenGL textures must be destroyed and recreated when you change resolution because you must also destroy and recreate your OpenGL context. A thread can have multiple contexts but only 1 may be current at time. The collection of textures, which flyweight stores in its factory, is logically bound to the current render context on the current thread.
So you need a per-context factory. Boost.Flyweight factories are global by design, the way I see them. So it's not ideal for what you want. You can get around this by putting the context in the key, however.
With the way OpenGL works only one context can be active at a time per thread. By using a custom hold I think I can change the active factory when I changed the active context. I need to think about the factory in the key thing more though.
participants (5)
-
Joaquin M Lopez Munoz
-
joaquin@tid.es
-
Mathias Gaunard
-
Michael Marcin
-
Scott McMurray