官术网_书友最值得收藏!

Generalizing the approach

We have implemented everything we need for textures, but we would like to handle other resources such as fonts and sound buffers too. As the implementation looks extremely similar for them, it would be a bad idea to write new classes FontHolder and SoundBufferHolder with exactly the same functionality. Instead, we write a class template, which we instantiate for different resource classes.

We call our template ResourceHolder and equip it with two template parameters:

  • Resource: The type of resource, for example, sf::Texture. We design the class template to work the SFML classes, but if you have your own resource class which conforms to the required interface (providing loadFromFile() methods), nothing keeps you from using it together with ResourceHolder.
  • Identifier: The ID type for resource access, for example, Textures::ID. This will usually be an enum, but the type is not restricted to enumerations. Any type that supports an operator< can be used as identifier, for example, std::string.

The transition from TextureHolder to ResourceHolder<Resource, Identifier> is straightforward. We replace the used types with the generic template parameters: sf::Texture becomes Resource, and Textures::ID becomes Identifier. Furthermore, we rename some variables to reflect the fact that we are talking about resources in general, not only textures. We also adapt the member functions accordingly.

Note

One thing we have to note when using templates is that the complete implementation needs to be in the header. We cannot use .cpp files for the method definitions anymore, but we would still like to separate interface and implementation. That is why we use a file ResourceHolder.hpp for the class definition, and a file ResourceHolder.inl for the method definitions. At the end of the .hpp file, we include the .inl file containing the implementation. .inl is a common file extension for inline template implementations.

The generalized class definition has the following interface:

template <typename Resource, typename Identifier>
class ResourceHolder
{
    public:
        void              load(Identifier id,const std::string& filename);

        Resource&         get(Identifier id);
        const Resource&   get(Identifier id) const;

    private:
        std::map<Identifier,std::unique_ptr<Resource>> mResourceMap;
};

The load() method can be written in the following way. Like before, we attempt to load the resource from a file, and then we insert the unique pointer into the map and make sure the insertion was successful:

template <typename Resource, typename Identifier>
void ResourceHolder<Resource, Identifier>::load(Identifier id, const std::string& filename)
{
    std::unique_ptr<Resource> resource(new Resource());
    if (!resource->loadFromFile(filename))
        throw std::runtime_error("ResourceHolder::load - Failed to load " + filename);

auto inserted = mResourceMap.insert(
std::make_pair(id, std::move(resource)));
    assert(inserted.second);
}

The two overloaded get() member functions are generalized using the same principle, that is why they are not listed here. You can refer to the online code base for a complete implementation.

Compatibility with sf::Music

As initially mentioned, the class sf::Music has semantics that are very different from other resource types. This begins already with its openFromFile() method that is not compatible to the loadFromFile() call in our implementation. Because of its streaming nature, a music object cannot be shared between multiple clients—each object represents one point in time of a certain music theme. No object contains the whole music data at once.

Instead of forcing sf::Music into a concept it does not fit, we decide to not store it inside ResourceHolder instances. This does not imply that there will be no encapsulation for music; we are rather going to cover music handling in-depth during Chapter 9, Cranking Up the Bass – Music and Sound Effects.

A special case – sf::Shader

There is one resource type we ave yet to cover: shaders. Since a SFML shader object can consist of a fragment and/or a vertex shader, the interface of sf::Shader deviates slightly from the other resource classes in SFML. sf::Shader provides two methods to load from a file:

bool loadFromFile(const std::string& filename, sf::Shader::Type type);
bool loadFromFile(const std::string& vertexShaderFilename, const std::string& fragmentShaderFilename);

The first function loads either a fragment or a vertex shader (which one is specified by the second parameter). The second function loads both a vertex and a fragment shader.

This interface is an issue for our generic implementation, because ResourceHolder::load() contains the following expression:

resource->loadFromFile(filename)

Which assumes that loadFromFile() is invoked with one argument. For sf::Shader, we have to specify two instead.

The solution is simple: we write an overloaded ResourceHolder::load() function that takes an additional parameter and forwards it directly as the second argument to sf::Shader::loadFromFile(). This parameter can have the type sf::Shader::Type or const std::string&. In order to cope with both types, we add a function template parameter. Our new load() function has the following declaration:

template <typename Parameter>
void load(Identifier id, const std::string& filename, const Parameter& secondParam);

The function definition is listed in the following code. Do not confuse yourself by the two template parameter lists; the first one is required for the class template ResourceHolder and the second one for the function template ResourceHolder::load(). In the function body, only the loadFromFile() call is different from before.

template <typename Resource, typename Identifier>
template <typename Parameter>
void ResourceHolder<Resource, Identifier>::load(Identifier id, const std::string& filename, const Parameter& secondParam)
{
    std::unique_ptr<Resource> resource(new Resource());
    if (!resource->loadFromFile(filename, secondParam))
        throw ...;

    ... // insertion like before
}

A nice side effect of this additional overload is that it enables other argument combinations for loadFromFile() too. The method of sf::Texture actually looks as shown in the following line of code:

bool loadFromFile(const std::string& filename, const IntRect& area = IntRect())

The default parameter can be used if we want the texture to load only a rectangular area of the image. Usually, we do not specify it and load the whole file, but thanks to our second load() overload, we have now the possibility to use this parameter.

In our complete implementation, we have written a separate function that inserts resources into the map, in order to reduce code duplication.

Using the new ResourceHolder class template, we can visualize a possible in-game situation in the following diagram. On the left you see two resource holders, one for textures and one for fonts. Each one contains a map of enumerators to resources. The player's Aircraft class on the right stores a sprite that points to a texture, as well as a text that points to a font.

主站蜘蛛池模板: 漳平市| 乌海市| 曲沃县| 文昌市| 林甸县| 德昌县| 甘孜| 利辛县| 平南县| 雷波县| 北安市| 西乌珠穆沁旗| 开原市| 兴文县| 屏东县| 林甸县| 武邑县| 都兰县| 闸北区| 嘉义县| 雅江县| 三明市| 扎兰屯市| 麻阳| 松江区| 锦州市| 星座| 思南县| 镇平县| 丹东市| 东乡| 呼图壁县| 壶关县| 深泽县| 萨迦县| 九江市| 北辰区| 灵台县| 大荔县| 高阳县| 乡城县|