Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Nimelrian

Alter Hase

  • »Nimelrian« ist der Autor dieses Themas

Beiträge: 1 216

Beruf: Softwareentwickler (aktuell Web/Node); Freiberuflicher Google Proxy

  • Private Nachricht senden

1

12.03.2014, 17:00

ResourceManager - Zeiger oder Referenzen?

Hallo zusammen,

da ich aktuell wieder in Hameln bin (zum Studieren) und demnach in meiner Freizeit wieder am Laptop sitze und programmieren kann, habe ich mich wieder meinem kleinen Missile-Command Clone gewidmet, der seit Dezember auf meiner Festplatte schlummert.

Ich habe darin erstmals einen Resource Manager verwendet, der Texturen bei Erstbenutzung lädt und sie dann bei Anfragen zurückgibt.

Hier der Code:

ResourceManager.hpp

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma once
#include <map>
#include <memory>
#include <SFML/Graphics/Texture.hpp>
class ResourceManager {
public:
    enum ID {Background, Terrain, Base, BaseDestroyed, Missile, SpriteCircle, SpriteSmoke, SpriteSmokeLight};
    ResourceManager();
    ~ResourceManager();
    sf::Texture* getTexture(ID id);
private:
    std::map<ID, std::unique_ptr<sf::Texture> > textureMap;
};


ResourceManager.cpp

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include "ResourceManager.hpp"

ResourceManager::ResourceManager() {
    textureMap.insert(std::make_pair(Background,        nullptr));
    textureMap.insert(std::make_pair(Terrain,           nullptr));
    textureMap.insert(std::make_pair(Base,              nullptr));
    textureMap.insert(std::make_pair(BaseDestroyed,     nullptr));
    textureMap.insert(std::make_pair(Missile,           nullptr));
    textureMap.insert(std::make_pair(SpriteCircle,      nullptr));
    textureMap.insert(std::make_pair(SpriteSmoke,       nullptr));
    textureMap.insert(std::make_pair(SpriteSmokeLight,  nullptr));
}

ResourceManager::~ResourceManager() {

}

sf::Texture* ResourceManager::getTexture(ID id) {
    std::unique_ptr<sf::Texture>& tex = textureMap.at(id);

    /*  
        tex is a nullptr when the texture hasn't been loaded yet.
        In this case, load the texture. Return the pointer with the loaded texture after loading.
        Otherwise, return it without doing anything, since the texture is already loaded.
    */
    if (tex == nullptr) {
        tex = std::unique_ptr<sf::Texture>(new sf::Texture());
        switch (id) {
        case Background:
            tex->loadFromFile("res/img/BG.png");
            break;
        case Terrain:
            tex->loadFromFile("res/img/Terrain.png");
            break;
        case Base:
            tex->loadFromFile("res/img/Base64.png");
            break;
        case BaseDestroyed:
            tex->loadFromFile("res/img/BaseDes64.png");
            break;
        case Missile:
            tex->loadFromFile("res/img/Missile.png");
            break;
        case SpriteCircle:
            tex->loadFromFile("res/img/SpriteCircle.png");
            break;
        case SpriteSmoke:
            tex->loadFromFile("res/img/SpriteSmoke.png");
            break;
        case SpriteSmokeLight:
            tex->loadFromFile("res/img/SpriteSmokeLight.png");
        }
    }
    return tex.get();
}


In der enum ID sind die verschiedenen verfügbaren Texturen gespeichert.
Die restliche Funktionalität dürfte durch den Kommentar in der .cpp geklärt sein.

Das ganze funktioniert auch wunderbar.
Nun ist natürlich die Frage, ob ich hier wirklich Zeiger zurückgeben will, denn verändert werden sollten die Texturen ja egtl. nicht.
Soll ich hier einfach eine const sf::Texture& zurückgeben?
Wobei ich mich dann frage, wie ich überprüfen kann, ob die Textur bereits geladen wurde. nullptr bringt bei normalen Objekten ja nichts. Hat da jemand Tipps?

Greets

Nim
Ich bin kein UserSideGoogleProxy. Und nein, dieses Forum ist kein UserSideGoogleProxyAbstractFactorySingleton.

Legend

Alter Hase

Beiträge: 731

Beruf: Softwareentwickler

  • Private Nachricht senden

2

12.03.2014, 17:53

Ich würde ein ID/Textur-Paar erst in die Map einfügen, wenn die Textur geladen wird. Dann ist eine Textur genau dann bereits geladen, wenn ihre ID in der Map gefunden wird.

Ich selbst lasse meinen Cache nur weak_ref's auf Objekte halten und gebe shared_ptr's raus. Wenn "draussen" keiner mehr das Objekt benutzt, wird es entladen. Dann muss man natürlich immer überprüfen, ob die weak_ref noch gültig ist.
"Wir müssen uns auf unsere Kernkompetenzen konzentrieren!" - "Juhu, wir machen eine Farm auf!"

Netzwerkbibliothek von mir, C#, LGPL: https://sourceforge.net/projects/statetransmitt/

NachoMan

Community-Fossil

Beiträge: 3 885

Wohnort: Berlin

Beruf: (Nachhilfe)Lehrer (Mathematik, C++, Java, C#)

  • Private Nachricht senden

3

12.03.2014, 20:10

Das switch-case ist doch eklig und auf die IDs kannst du verzichten. Verwende einfach den Dateinamen um die Texturen zu identifizieren. Ich glaube sf::Texture meldet sich automatisch beim Sprite ab, wenn es gelöscht wird. Da wird es keine Probleme mit der Gültigkeit geben, da man die Adresse nirgens wo anders zwischenspeichern muss. Ich würde "sf::Texture&" zurückgeben und einen String als Key benutzen.
Die Klasse soll doch nur dafür sorgen, dass keine Textur zweimal geladen wird oder?
"Der erste Trunk aus dem Becher der Erkenntnis macht einem zum Atheist, doch auf dem Grund des Bechers wartet Gott." - Werner Heisenberg
Biete Privatunterricht in Berlin und Online.
Kommt jemand mit Nach oMan?

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

4

12.03.2014, 20:27

Ich glaube sf::Texture meldet sich automatisch beim Sprite ab, wenn es gelöscht wird.
Was heißt "meldet sich ab?". Die Texture kennt das Sprite doch überhaupt nicht. Das Sprite referenziert dann lediglich eine ungültige Textur. Gerendert wird dann Müll, wenn die Textur vorzeitig gelöscht wird. Dort also mit Shared-Pointern zu hantieren kann ganz schnell ganz böse enden - in kaputten Grafiken.

Die Sache mit der ID haben wir ihm im IRC schon angemerkt ;)
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

NachoMan

Community-Fossil

Beiträge: 3 885

Wohnort: Berlin

Beruf: (Nachhilfe)Lehrer (Mathematik, C++, Java, C#)

  • Private Nachricht senden

5

12.03.2014, 20:53

Ich hatte die Erfahrung gemacht, dass es eben nicht passiert. Sobald die Textur freigegeben wurde schien das Sprite einfach nur weiß zu sein. Ich konnte mir das damals nur damit erklären, dass die Textur alle seine Sprites zwischenspeichert. Aber vielleicht habe ich mich auch geirrt. Es ist ja ne ganze Weile her. Vielleicht schau ich demnächst mal im SFML Code nach.

Solang du dafür sorgst, dass die Texturen erst am Ende des Programms freigeben werden sollte es keine Probleme geben. Heutzutage hat man sowieso mehr als genug Speicher für 10 kleine 2D Spiele und das neuladen von der Festplatte dauert ewig.
"Der erste Trunk aus dem Becher der Erkenntnis macht einem zum Atheist, doch auf dem Grund des Bechers wartet Gott." - Werner Heisenberg
Biete Privatunterricht in Berlin und Online.
Kommt jemand mit Nach oMan?

Nimelrian

Alter Hase

  • »Nimelrian« ist der Autor dieses Themas

Beiträge: 1 216

Beruf: Softwareentwickler (aktuell Web/Node); Freiberuflicher Google Proxy

  • Private Nachricht senden

6

12.03.2014, 21:19

Zur Klärung eures kleinen Konflikts: Ist die Textur eines Sprites beim Zeichnen des Sprites nicht mehr gültig, wird das Sprite einfach nur weiß dargestellt (SFML2.1) :)

Ansonsten ist hier der aktuelle Stand des RM:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma once
#include <map>
#include <memory>
#include <iostream>
#include <SFML/Graphics/Texture.hpp>
class ResourceManager {
public:
    //enum ID {Background, Terrain, Base, BaseDestroyed, Missile, SpriteCircle, SpriteSmoke, SpriteSmokeLight};
    ResourceManager();
    ~ResourceManager();
    sf::Texture* getTexture(const std::string& filename);
private:
    std::map<std::string, std::unique_ptr<sf::Texture> >    textureMap;
};


C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "ResourceManager.hpp"

ResourceManager::ResourceManager() {
    textureMap.insert(std::make_pair("BG",                  nullptr));
    textureMap.insert(std::make_pair("Terrain",             nullptr));
    textureMap.insert(std::make_pair("Base64",              nullptr));
    textureMap.insert(std::make_pair("BaseDes64",           nullptr));
    textureMap.insert(std::make_pair("Missile",             nullptr));
    textureMap.insert(std::make_pair("SpriteCircle",        nullptr));
    textureMap.insert(std::make_pair("SpriteSmoke",         nullptr));
    textureMap.insert(std::make_pair("SpriteSmokeLight",    nullptr));
}

ResourceManager::~ResourceManager() {

}

sf::Texture* ResourceManager::getTexture(const std::string& filename) {
    std::unique_ptr<sf::Texture>& tex = textureMap.at(filename);

    /*  
        tex is a nullptr when the texture hasn't been loaded yet.
        In this case, load the texture. Return the pointer with the loaded texture after loading.
        Otherwise, return it without doing anything, since the texture is already loaded.
    */
    if (tex == nullptr) {
        tex = std::unique_ptr<sf::Texture>(new sf::Texture());
        try {
            if (!(tex->loadFromFile("res/img/" + filename + ".png")))
                throw filename;
        }
        catch (const std::string& filename) {
            std::cout << ("The file \"" + filename + "\" can not be found!") << std::endl;
        }
    }
    return tex.get();
}


Besser?
Ich bin kein UserSideGoogleProxy. Und nein, dieses Forum ist kein UserSideGoogleProxyAbstractFactorySingleton.

NachoMan

Community-Fossil

Beiträge: 3 885

Wohnort: Berlin

Beruf: (Nachhilfe)Lehrer (Mathematik, C++, Java, C#)

  • Private Nachricht senden

7

12.03.2014, 21:26

Der Inhalt des Konstruktors ist überflüssig und du solltest eine Referenz zurückgeben. Der Nutzer sollte die Endung und den Pfad selbst bestimmen können. Die Endung ist abhängig von der Datei, sollte also im Parameter "filename" stehen. Den Pfad würde ich im Konstruktor entgegennehmen. Das Werfen und Auffangen der Exception kannst du dir doch sparen. Gib einfach die Fehlermeldung aus, wirf die Exception ohne sie gleich wieder aufzufangen oder mach beides. Den Destruktor kannst du auch löschen.
"Der erste Trunk aus dem Becher der Erkenntnis macht einem zum Atheist, doch auf dem Grund des Bechers wartet Gott." - Werner Heisenberg
Biete Privatunterricht in Berlin und Online.
Kommt jemand mit Nach oMan?

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »NachoMan« (12.03.2014, 21:36)


Legend

Alter Hase

Beiträge: 731

Beruf: Softwareentwickler

  • Private Nachricht senden

8

12.03.2014, 21:51

Ich glaube das wird dann erstmal überhaupt nicht funktionieren. Wenn der in getTexture übergebene Dateiname der textureMap-Map nicht bekannt ist, gibt das ne Exception .. Für die Einträge wo ID und Dateiname gleich sind, wird das dann momentan funktionieren, da im Konstruktor die Einträge angelegt worden sind.

Du solltest mit vorher mit find prüfen, ob ein Eintrag vorhanden ist und nur dann den Eintrag abholen. Dann kannst du auch die Vorschläge von Nachoman umsetzen.
"Wir müssen uns auf unsere Kernkompetenzen konzentrieren!" - "Juhu, wir machen eine Farm auf!"

Netzwerkbibliothek von mir, C#, LGPL: https://sourceforge.net/projects/statetransmitt/

Nimelrian

Alter Hase

  • »Nimelrian« ist der Autor dieses Themas

Beiträge: 1 216

Beruf: Softwareentwickler (aktuell Web/Node); Freiberuflicher Google Proxy

  • Private Nachricht senden

9

12.03.2014, 21:51

Der Inhalt des Konstruktors ist überflüssig und du solltest eine Referenz zurückgeben. Der Nutzer sollte die Endung und den Pfad selbst bestimmen können. Die Endung ist abhängig von der Datei, sollte also im Parameter "filename" stehen. Den Pfad würde ich im Konstruktor entgegennehmen. Das Werfen und Auffangen der Exception kannst du dir doch sparen. Gib einfach die Fehlermeldung aus, wirf die Exception ohne sie gleich wieder aufzufangen oder mach beides. Den Destruktor kannst du auch löschen.


C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma once
#include <map>
#include <memory>
#include <SFML/Graphics/Texture.hpp>
class ResourceManager {
public:
    //enum ID {Background, Terrain, Base, BaseDestroyed, Missile, SpriteCircle, SpriteSmoke, SpriteSmokeLight};
    ResourceManager(const std::string& resourcePath);
    sf::Texture* getTexture(const std::string& filename);
private:
    std::string resourcePath;

    std::map<std::string, std::unique_ptr<sf::Texture> > resourceMap;
};


C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "ResourceManager.hpp"

ResourceManager::ResourceManager(const std::string& resourcePath) {
    this->resourcePath = resourcePath;
}

sf::Texture* ResourceManager::getTexture(const std::string& filename) {
    std::unique_ptr<sf::Texture> tex = std::move(resourceMap.at(filename));

    /*  
        tex is a nullptr when the texture hasn't been loaded yet.
        In this case, load the texture. Return the pointer with the loaded texture after loading.
        Otherwise, return it without doing anything, since the texture is already loaded.
    */
    if (tex == nullptr) {
        tex = std::unique_ptr<sf::Texture>(new sf::Texture());
        if (!(tex->loadFromFile(resourcePath + filename)))
            throw ("The file \"" + filename + "\" can not be found at " + resourcePath + "!");
        resourceMap.insert(std::make_pair(filename, std::move(tex)));
    }
    return tex.get();
}


Wie soll ich die Referenz implementieren? Wäre tex in der .cpp kein Pointer, könnte ich ja nicht prüfen, ob die Textur noch nicht geladen wurde? (Kann auch sein, dass ich hier einen Denkfehler habe, aber ich habe 4 Monate nicht mit C++ gearbeitet und bin um die Uhrzeit auch schon etwas müde :wacko:
BC hat im Chat auch vorgeschlagen, einen const-ptr zurückzugeben.
Ich bin kein UserSideGoogleProxy. Und nein, dieses Forum ist kein UserSideGoogleProxyAbstractFactorySingleton.

NachoMan

Community-Fossil

Beiträge: 3 885

Wohnort: Berlin

Beruf: (Nachhilfe)Lehrer (Mathematik, C++, Java, C#)

  • Private Nachricht senden

10

12.03.2014, 22:17

Ich sehe darin kein Problem.

C-/C++-Quelltext

1
2
sf::Texture& ResourceManager::getTexture(const std::string& filename)
return *tex; // oder return *(tex.get()); ist lang her^^

Ich glaube das wird dann erstmal überhaupt nicht funktionieren. Wenn der in getTexture übergebene Dateiname der textureMap-Map nicht bekannt ist, gibt das ne Exception ...

Da hat sich dann aber einer von uns verlesen...
"Der erste Trunk aus dem Becher der Erkenntnis macht einem zum Atheist, doch auf dem Grund des Bechers wartet Gott." - Werner Heisenberg
Biete Privatunterricht in Berlin und Online.
Kommt jemand mit Nach oMan?

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »NachoMan« (12.03.2014, 22:22)


Werbeanzeige