Ressourcenverwaltung (besonders Freigabe)



  • Meine Befürchtung bei 1b) ist eben auch, dass bei einem "Enforce-Release" plötzlich Ressourcenbenutzer unvorbereitet vor dem Nichts stehen und seelenruhig Nullzeiger dereferenzieren. Andererseits hab ich mir auch das mit der Kontrolle überlegt – mit dem Hintergedanken, dass eine Art Resource Leak entstehen kann, wenn ein ResourcePtr irgendwo nicht mehr freigegeben wird. Aber das ist dann wohl eher ein Benutzerfehler, den man nicht speziell berücksichtigen sollte. Die Entscheidung läuft darauf hinaus, ob ResourcePtr eine starke oder schwache Referenz darstellt. Kann sich jemand sonst einen gerechtfertigten Fall für Ressourcen-Entziehen (also Weak-References) vorstellen?

    Dass Release() das AutoRelease-Flag für die Ressource automatisch setzt, ist eine interessante Idee (ganz nach dem Prinzip "wenn du schon jetzt nicht freigeben kannst, tu das wenigstens so schnell wie möglich"). Momentan gibt es nur ein Flag für den ganzen Ressourcen-Manager, aber es könnte sich vielleicht lohnen, dieses pro Ressource zu setzen.

    Danke vielmals für die Antwort!


  • Mod

    resourcen haben oft an sich dass dem user der inhalt nicht wirklich wichtig ist, er nur das resourcen objekt verwaltet, dabei bietet es sich also an proxy resourcen zu erstellen die auf richtige resourcen verweisen.
    dabei kannst du dann
    - ein proxy zurueckgeben ohne dass die resource ueberhaupt schon da ist (z.B. weil das streaming noch nicht fertig ist)
    - du kannst eine dummy resource referenzieren (z.b. weil eine resource ueberhaupt nicht vorhanden ist, weil vielleicht jemand dir ein level gegeben hat und du noch nicht z.b. die neusten texturen hast)
    - du kannst resourcen abaendern (z.b. wird der speicher knapp und eine weniger wichtige 'specular map' wird um 2 mipmaps reduziert damit eine 'diffuse map' geladen werden kann).

    wenn du wirklich nur acquire und release haettest, was nur refcounted, dann braeuchtest du dir nicht die muehe eines resource managers machen, da wuerde eine simple factory und recounting ausreichen, das kannst du dann fuer mehr als 'resources' benutzen.
    willst du wirklich resources haben, hat niemand einen pointer auf die eigentliche resource, nur auf das verwaltungsobjekt. um zugriff auf die resource zu erhalten, bietest du dann entweder map/unmap und/oder read/write funktionen an.



  • Die Idee mit Proxy-Ressourcen klingt gut, doch ich frage mich, ob mir die Indirektion momentan so viel bringt. Mein Ressourcen-Manager ist zur Zeit nicht viel mehr als ein Wrapper um std::map mit sicherem Zugriff über die Smart-Pointers und automatischem Laden (siehe Punkte im ersten Post). Ich habe mir auch überlegt, in Zukunft Multithreading einzusetzen, um z.B. nicht-blockierend laden zu können. Dann würden Proxies immer mehr an Bedeutung erlangen. Den Vorschlag werde ich mir merken, danke!

    Da ich mit SFML arbeite, geht es mir vorerst hauptsächlich um Ressourcen wie Bilder, Schriftarten oder Sounds. Dabei gibt es schon eine Indirektion (z.B. durch Sprites, die während ihrer ganzen Lebenszeit auf Bilder verweisen). Ich habe das Gefühl, dass Ressourcen nicht gross herumgereicht werden, bevor man auf sie zugreift, und dass ein separates Read/Write den Status Quo nur verkomplizieren würde. Aber gut möglich, dass ich später alles noch flexibler mache.



  • Wie würdet ihr ausserdem darauf reagieren, wenn Release() eine ungültige Ressourcen-ID übergeben wird?

    Meiner Ansicht nach ist das Freigeben nicht vorhandener Ressourcen ein Logikfehler, der bei korrektem Code nie vorkommt. Daher tendiere ich zu assert .


  • Mod

    ich wuerde schon dazu tendieren bei einer falschen ID ein debugbreak zu setzen bzw exception zu werfen.
    Wenn man die IDs in einer klasse kapselt, sollte das eigentlich NIE passieren, wenn es dennoch passieren sollte, ist entweder etwas komplett schief gelaufen (z.B. race durch multithreading), es gibt einen wilden pointer oder sonst ein seltsammer grund (was man also wirklich nicht erwartet).
    Dabei kann man garnicht strikt genug dagegen vorgehen, denn genauso gut wie eine falsche ID koennte es sein, dass an einer anderen stelle eine ID verwendet wird die schon ein anderes objekt representiert (sprich: etwas wird freigegeben, an die stelle wird was neues allokiert und dann kommt die ID fuer das schon freigegebene objekt, das bleibt unbemerkt).

    wenn es geht, IDs vermeiden und auf objekten arbeiten, wuerde ich sagen :).



  • Okay, danke für die Bestätigung. Ja, es handelt sich momentan nicht nur um eine Integer-ID, sondern ein Objekt, das die spezifischen Ressourceneigenschaften kapselt (und Ressourcen eindeutig voneinander unterscheiden kann).

    Und nun stehe ich schon wieder vor der nächsten Frage: Soll die Release-Strategie (AutoRelease oder ExplicitRelease, siehe oben) im Konstruktor festgelegt werden oder würdet ihr es sinnvoll finden, diese nachträglich zu ändern? Da das Flag pro Ressource existiert, müsste ich auch festlegen, ob bei einer Änderung alle Ressourcenflags geändert werden, oder nur solche, die ab jetzt geladen werden...

    Wahrscheinlich denke ich aber wiedermal zu kompliziert 🙂



  • Ich werde nachträgliche Strategie-Änderungen vorerst zulassen. Am flexibelsten bin ich wohl mit Callbacks über std::tr1::function , sodass der Benutzer auch seine eigenen Freigabe- und Ladefehler-Strategien angeben kann.

    Jetzt muss ich "nur" noch einen Weg finden, das einigermassen sauber unter einen Hut zu bringen und die Semantik möglichst intuitiv zu gestalten...



  • ad 1)
    Ganz klar a: Resourcen auf die noch ein Shared-Pointer exisitert nicht freigeben. Grund: würde man die Zeiger einfach NULL machen, müssten alle Programmteile die mit solchen Zeigern hantieren damit klarkommen, wenn diese einfach mal so NULL werden. Speziell im Zusammenhang mit Multithreading sehr problematisch, aber auch ohne wohl zumindest sehr lästig.

    ad 2)
    Ich persönlich würde keine Exceptions werfen, und zwar weil das Freigeben von Dingen immer "no-throw" sein sollte. U.a. damit man es in Destruktoren erledigen kann. Den "ungültige ID" Fall würde ich mit einem ASSERT() behandeln und den "wird noch gebraucht" Fall würde ich vermutlich einfach explizit erlauben. In beiden Fällen sollte der Release-Code einfach nichts tun. Oder aber bei "ungültige ID" ne Logausgabe schreiben und das Programm sofort anhalten. Auf jeden Fall nicht Exception werfen und weiterlaufen lassen.



  • hustbaer schrieb:

    Grund: würde man die Zeiger einfach NULL machen, müssten alle Programmteile die mit solchen Zeigern hantieren damit klarkommen, wenn diese einfach mal so NULL werden. Speziell im Zusammenhang mit Multithreading sehr problematisch, aber auch ohne wohl zumindest sehr lästig.
    [...]
    Ich persönlich würde keine Exceptions werfen, und zwar weil das Freigeben von Dingen immer "no-throw" sein sollte.

    Das sind gute Argumente, vielen Dank!

    hustbaer schrieb:

    Den "ungültige ID" Fall würde ich mit einem ASSERT() behandeln und den "wird noch gebraucht" Fall würde ich vermutlich einfach explizit erlauben.

    So mach ichs jetzt gerade. Ein bool -Rückgabetyp könnte vielleicht nützlich sein, damit der Aufrufer überprüfen kann, ob seine Freigabe geklappt hat.

    • Ungültige ID -> Assertion bricht ab
    • Gültige ID, aber noch benutzt -> return false
    • Gültige ID, kann freigegeben werden -> return true

    Allerdings könnte ich evtl. auch eine zusätzliche Methode bool IsInUse(ID id) schreiben, um das explizit zu überprüfen. Aber demnächst bleibe ich wohl beim Alten.

    Was die Strategien betrifft: Ich tendiere momentan zu einfachen enum s, bis ich ein gutes Design entwickelt habe, das benutzerdefinierte Reaktionen ermöglicht. Ist halt noch nicht so flexibel, aber sollte für viele Fälle reichen und ist einfach zu verstehen.



  • Ja, ein bool als Returnwert kann in dem Fall nicht schaden. Bzw. ist sogar guter Stil.
    Informationen an die man bei der Ausführung einer Funktion "gratis" drankommt, und die für den Aufrufer von Nutzen sein könnten, sollte man auch zurückgeben.

    Wobei man natürlich immer den potentiellen Nutzen gegen die Kosten abwägen muss, aber bei einer Funktion die sonst keine Werte zurückgibt, sind die Kosten nen bool zurückzugeben quasi nicht-existent.



  • hustbaer schrieb:

    Informationen an die man bei der Ausführung einer Funktion "gratis" drankommt, und die für den Aufrufer von Nutzen sein könnten, sollte man auch zurückgeben.

    Genau, das war mein Hintergedanke. Hat das nicht mal Alexander Stepanov oder so gesagt?



  • Jopp, das erwähnt er in einem seiner Papers 🙂


Anmelden zum Antworten