Moderators Kercyn Δημοσ. 24 Μαΐου 2014 Moderators Δημοσ. 24 Μαΐου 2014 Καλησπέρα σας κυρίες και κύριοι Insomniacs. Έχω το ακόλουθο πρόβλημα και θα ήθελα τη βοήθειά σας. Έχω μια κλάση cResourceManager και άλλη μία cMouseCursor. Το cursor ζητάει ένα texture από το manager, ο manager το φτιάχνει και το επιστρέφει, αυξάνοντας ένα counter που λέει ότι το συγκεκριμένο texture το χρησιμοποιούν X αντικείμενα. Στον destructor του cursor καλείται η συνάρτηση releaseTexture, η οποία μειώνει το μετρητή του texture και άμα φτάσει στο 0 τότε το διαγράφει. Το πρόβλημά μου είναι ότι όταν τελειώνει το πρόγραμμα, τρέχει πρώτα ο destructor του manager και μετά αυτός του cursor, με αποτέλεσμα να παίρνω ένα exception γιατί πάω να προσπελάσω ένα αντικείμενο που δεν υπάρχει. Αυτό που σκέφτηκα να κάνω είναι να μην καλώ τη releaseTexture στο destructor του cursor και στο destructor του manager να καθαρίζω ό,τι asset έχει μείνει. Αυτό δε μ' αρέσει γιατί μου φαίνεται "λάθος" ένα object να μην καθαρίζει τα πράγματά του όταν τελειώνει (σε αυτή την περίπτωση δεν καθαρίζει τίποτα, απλώς λέει στο manager ότι το συγκεκριμένο asset το χρησιμοποιεί ένας λιγότερος). Βέβαια το πρόβλημα αυτό συμβαίνει μόνο σε όσα objects είναι στο ίδιο scope με το manager (ο οποίος υπάρχει σε ένα SharedResources αρχείο το οποίο γίνεται include όπου χρειάζεται) και θα μπορούσα απλώς να εφαρμόσω την παραπάνω λύση. Αυτό που θέλω να ξέρω είναι τι μπορώ να κάνω πέρα απ' αυτό και αν υπάρχει κάποιος τρόπος να αντιμετωπίζω παρόμοια προβλήματα. Ευχαριστώ πολύ.
migf1 Δημοσ. 24 Μαΐου 2014 Δημοσ. 24 Μαΐου 2014 Δεν ξέρω αν μπορώ να βοηθήσω γενικώς, αλλά δεν έχω καταλάβει τι κάνεις. Από αυτό που γράφεις, καταλαβαίνω πως το μισό reference counting (increament) για το texture γίνεται στον manager και το άλλο μισό (decrement) γίνεται στο MouceCursor? Επίσης, το construction ενός texture γίνεται στον manager, αλλά το destruction στο mousecursor; Η releaseTexture() είναι μέθοδος του manager ή του mousecursor? Τα objects της MouseCursor πώς επικοινωνούν με τον Manager? Έχεις φτιάξει κάποιο messaging protocol; Γενικώς, μου φαίνεται πολύ μπερδεμένο πράγμα (ίσως όμως να μην σε κατάλαβα και καλά).
Moderators Kercyn Δημοσ. 24 Μαΐου 2014 Μέλος Moderators Δημοσ. 24 Μαΐου 2014 Έχω ένα object (resmgr) τύπου cResourceManager κι ένα άλλο (cursor) τύπου cMouseCursor στο ίδιο αρχείο (SharedResources). Η δουλειά του resmgr είναι να δημιουργεί και να κρατάει τα assets που χρησιμοποιεί το πρόγραμμα και να δίνει references σε αυτά τα assets σε όποιο object του ζητάει. Το cursor, όταν ξεκινάει, χρειάζεται ένα texture, οπότε ζητάει απ' τον resmgr το reference (ίσως ο πιο σωστός όρος είναι "handle") σε αυτό το texture περνώντας του ένα texture ID. O resmgr δημιουργεί αυτό το texture βάσει το id που του έδωσε ο cursor κι επιστρέφει το handle πίσω στον cursor. Άμα κάποιο άλλο object ζητήσει το ίδιο texture, τότε ο resmgr δε χρειάζεται να το ξαναφορτώσει γιατί το έχει ήδη αποθηκεύσει στο cache που έχει για τα textures. Κρίνω, όμως, άσκοπο να υπάρχει ένα texture στο cache όταν δεν το χρησιμοποιεί κανείς. Γι' αυτό έχω ένα άλλο map που μετράει πόσα objects χρησιμοποιούν ένα συγκεκριμένο texture. Όταν ο μετρητής για ένα texture γίνει 0, τότε το texture διαγράφεται απ' το cache. Όταν κάποιο object δε χρειάζεται άλλο ένα texture (όταν, δηλαδή, καλείται ο destructor του), τότε στέλνει σήμα στον resmgr να μειώσει το counter για το συγκεκριμένο texture (καλώντας τη συνάρτηση releaseTexture του resmgr). Το πρόβλημα είναι ότι ο resmgr και o cursor είναι στο ίδιο scope και καλείται ο destructor του resmgr πριν από το destructor του cursor. Έτσι, όταν ο destructor του cursor πάει να καλέσει τη releaseTexture, το πρόγραμμα χτυπάει γιατί το map με τα counters δεν υπάρχει πια. Παραθέτω και τον κώδικα μήπως και βγάλουν περισσότερο νόημα αυτά που λέω. Ό,τι σχόλιο έχετε για τον κώδικα είναι ευπρόσδεκτο, γιατί τώρα ξεκινάω να μαθαίνω τα smart pointers και πώς να φτιάχνω έναν resource manager. (τα βάζω στο ideone γιατί εδώ μου χαλάει το indentation...) cResourceManager.hpp http://ideone.com/rWgVNT cResourceManager::requestTexture http://ideone.com/J4VkDt cResourceManager::releaseTexture http://ideone.com/IhJVjx
Moderators Kercyn Δημοσ. 24 Μαΐου 2014 Μέλος Moderators Δημοσ. 24 Μαΐου 2014 Kercyn check here. Το έχω δει αυτό φίλε Timonkaipumpa (όπως και πολλά άλλα), αλλά απ' αυτά που διάβασα κατάλαβα ότι ένας shared_ptr είναι πιο ακριβός από έναν unique_ptr, συν ότι δεν χρειάζεται (και δεν πρέπει) να έχω αντίγραφα ενός asset από δω κι από κει. Ο resmgr είναι ο μόνος που έχει ownership σε ένα asset και τα άλλα objects απλώς θα παίρνουν ένα handle.
παπι Δημοσ. 24 Μαΐου 2014 Δημοσ. 24 Μαΐου 2014 Θα φτιαξεις μια κλαση reference counter πχ Object, και πανω σε αυτη θα βαλεις οτι θες να εχει δικοσου lifetime.
Moderators Kercyn Δημοσ. 24 Μαΐου 2014 Μέλος Moderators Δημοσ. 24 Μαΐου 2014 Θα φτιαξεις μια κλαση reference counter πχ Object, και πανω σε αυτη θα βαλεις οτι θες να εχει δικοσου lifetime. Μα αυτό δεν κάνω κι εδώ (ή τουλάχιστον προσπαθώ να κάνω); Το θέμα είναι ότι δε θέλω ο cursor να έχει δικό του texture, θέλω να το παίρνει απ' τον resmgr.
παπι Δημοσ. 24 Μαΐου 2014 Δημοσ. 24 Μαΐου 2014 Εσυ εχεις φτιαξει εναν manager με ολα τα αρνητικα που εχεις ενα τετοιο πραμα. Εγω σου λεω να φτιαξεις μια base class που θα ειναι ενας ref counter. check
Moderators Kercyn Δημοσ. 24 Μαΐου 2014 Μέλος Moderators Δημοσ. 24 Μαΐου 2014 Ναι αλλά το factory δε θα φτιάχνει ένα καινούριο resource κάθε φορά (και πιθανότατα ένα resource του οποίου τα data υπάρχουν ήδη στη μνήμη); Το παράδειγμα που μου έδωσες δεν παρομοιάζει τη λειτουργία ενός shared_ptr; Επίσης, τι κακά έχει ένας resource manager; Επειδή παιχνίδι θέλω να φτιάξω, βλέπω τόσα και τόσα βιβλία που λένε ένας σωστός resource manager είναι σημαντικός και μπλα μπλα μπλα...
παπι Δημοσ. 24 Μαΐου 2014 Δημοσ. 24 Μαΐου 2014 Το σωστο resource manager δεν εχει να κανει με τα lifetime των objects. Οταν αναλαμβανει ενας τριτος το lifetime τοτε εχεις τα ωραια που εγραψες στην αρχη. Για αυτα που λες στην παρενθεση, αυτο θα το κοιταξεις στο factory.
Moderators Kercyn Δημοσ. 24 Μαΐου 2014 Μέλος Moderators Δημοσ. 24 Μαΐου 2014 Κάτι δε μου κολλάει πάντως. Την αποκλειστικότητα ενός asset την έχει ο resource manager. Αυτός είναι υπεύθυνος να δημιουργήσει το asset και να το καταστρέψει όταν δεν το χρησιμοποιεί κανείς. Το πρόβλημά μου δεν είναι θέμα lifetime, μιας και το ένα και μοναδικό asset XYZ ανήκει στο resource manager και καταστρέφεται όταν το πει αυτός. Για αυτα που λες στην παρενθεση, αυτο θα το κοιταξεις στο factory Άρα θα πρέπει να έχουμε κι ένα cache μέσα στο factory και να βλέπουμε το __refs του κάθε asset για να το σβήσουμε. Άμα όμως φύγει ο manager, τότε όλα τα αντικείμενα που χρησιμοποιούν assets και δεν έχουν προλάβει να καταστραφούν θα χτυπήσουν. Δίνω ένα παράδειγμα γιατί νομίζω ότι δεν έχω γίνει κατανοητός: Εσύ είσαι ένα object μιας κλάσης, o migf1 ένα άλλο object της ίδιας κλάσης κι εγώ ο resource manager. Για να λειτουργήσεις σωστά, χρειάζεσαι ένα texture. Απευθύνεσαι, λοιπόν, σε μένα, ρωτώντας με αν έχω το texture που χρειάζεσαι. Μιας και είσαι ο πρώτος που μου ζητάει αυτό το texture, εγώ το φορτώνω και σου λέω "μπορείς να βρεις το texture που θες εκεί". Παράλληλα, σημειώνω ότι 1 object μου έχει ζητήσει το συγκεκριμένο texture. Έρχεται μετά κι ο migf1, ζητώντας μου το ίδιο texture. Εφ' όσον έχω ήδη φορτώσει το texture για σένα, δείχνω στο migf1 πού να το βρει. Υπογραμμίζω ότι και οι 2 χρησιμοποιείτε το ίδιο texture, δε φτιάχνω ένα καινούριο για το migf1. Φεύγει κι ο migf1 κι εγώ σημειώνω ότι 2 objects χρησιμοποιούν αυτό το texture. Έρχεσαι μετά εσύ και μου λες "εγώ τελείωσα, δε θέλω άλλο αυτό το texture". Μειώνω, λοιπόν, εγώ τον counter και τον κάνω 1. Έρχεται μετά κι ο migf1 και μου λέει ότι τελείωσε κι αυτός. Μειώνω κι άλλο τον counter, και τώρα το texture δεν το χρησιμοποιεί κανείς, άρα το σβήνω. Άμα όμως εμένα μου καπνίσει και σηκωθώ και φύγω πριν τελειώσετε και οι 2, τότε το texture που χρησιμοποιείτε όχι μόνο θα έχει χαθεί, αλλά δε θα μπορείτε να με βρείτε για να μου πείτε ότι τελειώσατε. Εκεί στο σημείο που δε μπορείτε να με βρείτε χτυπάει το exception. Πώς γίνεται λοιπόν εγώ (ο resource manager) να αναγκαστώ να φύγω τελευταίος;
nplatis Δημοσ. 24 Μαΐου 2014 Δημοσ. 24 Μαΐου 2014 Πέρα από τη συζήτηση της αρχιτεκτονικής, μια πρόταση για το πρόβλημά σου. Όταν γράφεις ότι πρώτα καταστρέφεται ο resource manager και μετά ο cursor, αυτό σημαίνει ότι δηλώνεις πρώτα τον cursor και μετά τον resource manager: στη C++ τα αντικείμενα καταστρέφονται σε αντίστροφη σειρά από αυτή που δηλώνονται. Επομένως, είτε αντιστρέφεις τη σειρά που τους δηλώνεις, είτε τους δημιουργείς δυναμικά ως δείκτες, με new, οπότε ελέγχεις πλήρως πότε θα καταστραφεί ο καθένας.
παπι Δημοσ. 24 Μαΐου 2014 Δημοσ. 24 Μαΐου 2014 Κάτι δε μου κολλάει πάντως. Την αποκλειστικότητα ενός asset την έχει ο resource manager. Αυτός είναι υπεύθυνος να δημιουργήσει το asset και να το καταστρέψει όταν δεν το χρησιμοποιεί κανείς. Το πρόβλημά μου δεν είναι θέμα lifetime, μιας και το ένα και μοναδικό asset XYZ ανήκει στο resource manager και καταστρέφεται όταν το πει αυτός.Την αποκλειστικοτητα την εχει αυτος που το δουλευει. Με αυτη την λογικη δουλευει ο reference counter. Άρα θα πρέπει να έχουμε κι ένα cache μέσα στο factory και να βλέπουμε το __refs του κάθε asset για να το σβήσουμε.Καπου εκει θα μπει ο manager. Και η δουλεια του θα ειναι απλη. Υπαρχει το "δεντρο" στη vram; ναι, δωστο. Οχι, φορτωσε το και δωστο. Αν θες πιο συνθετη, τοτε θα κανεις και preload (βεβαια τωρα θα αερολογουμε επειδη δεν εχουμε καποια εικονα του προγραμματος). Άμα όμως φύγει ο manager, τότε όλα τα αντικείμενα που χρησιμοποιούν assets και δεν έχουν προλάβει να καταστραφούν θα χτυπήσουν. Οχι, επειδη οπως ειπα στην αρχη, την αποκλειστικοτητα την εχει αυτος που το δουλευει.
migf1 Δημοσ. 24 Μαΐου 2014 Δημοσ. 24 Μαΐου 2014 Αυτό που περιγράφεις μου ακούγεται αρκετά όμοιο με αυτό εδώ: http://stackoverflow.com/questions/15707991/good-design-pattern-for-manager-handler(έχει και πλήρη κώδικα... no thanks, δεν θέλω να τον διαβάσω εγώ ... του έριξα μια ματιά όμως ) Μια άλλη παραπλήσια ιδέα, είναι το referencing counting να το κάνουν τα ίδια τα resource-objects μέσα τους (και όχι ο resource manager) αλλά να έχουν πρόβλεψη και για forced destruction, π.χ. σε περίπτωση που ο manager θελήσει να τα σκοτώσει και να κλείσει το πρόγραμμα, ή για οποιονδήποτε άλλο λόγο, να τους στέλνει ένα message "kill yourself" (δηλαδή να μην βασίζουν το destruction τους αποκλειστικά στο εσωτερικό τους reference counting). Αυτό νομίζω είναι που σου λέει και ο πάπι. Nα έχεις δηλαδή ένα resource-object factory ξεχωριστό από τον resource manager, αλλά προφανώς να μπορούν να επικοινωνούν. Εννοείται πως ο resource manager θα έχει ανά πάσα στιγμή γνώση των resource-objects που είναι ήδη instantiated, ως kinds, αλλά δεν χρειάζεται να κρατάει και reference-counting για το καθένα τους (μπορεί να κρατάει μονάχα pointers προς κάθε instantiated kind of object, ή kind ids, ή οτιδήποτε σχετικό) για να τα περνάει στους consumers, όταν πάρει clearance από το factory. EDIT: Δες κι αυτό που είπε ο nplatis, αν και ισχύει μονάχα για static objects.
defacer Δημοσ. 24 Μαΐου 2014 Δημοσ. 24 Μαΐου 2014 Το έχω δει αυτό φίλε Timonkaipumpa (όπως και πολλά άλλα), αλλά απ' αυτά που διάβασα κατάλαβα ότι ένας shared_ptr είναι πιο ακριβός από έναν unique_ptr, συν ότι δεν χρειάζεται (και δεν πρέπει) να έχω αντίγραφα ενός asset από δω κι από κει. Ο resmgr είναι ο μόνος που έχει ownership σε ένα asset και τα άλλα objects απλώς θα παίρνουν ένα handle. Ένα (γενικό) σχόλιο σχετικό μ' αυτό είναι πως θα μπορούσες όντως να χρησιμοποιήσεις shared_ptr -- το "handle" συνήθως σημαίνει πως "είναι opaque όσον αφορά αυτούς που το χρησιμοποιούν", πράγμα που το κάνεις για να είναι αναγκασμένοι όλοι όσοι το χρησιμοποιούν να περνάνε από σένα κάθε φορά που το χρησιμοποιούν. Στη δική σου περίπτωση δε φαίνεται να συμβαίνει αυτό μιας και δεν έχεις άλλες λειτουργίες εκτός από request και release οπότε δεν υπάρχει και λόγος να χρησιμοποιείς "handle". Το ίδιο καλά μπορεί να χειριστεί το lifetime του αντικειμένου ο shared_ptr όπως και ο resource manager σου. Αυτά όλα ακαδημαϊκά μιας και πρώτον δεν είναι αυτή η καλύτερη λύση για την περίπτωσή σου και δεύτερον γιατί οι πολλοί shared_ptr θα είναι πιθανόν χειρότεροι από ένα centralized tracker και γράφεις game (perf critical και καλά). Το πρόβλημα είναι ότι ο resmgr και o cursor είναι στο ίδιο scope και καλείται ο destructor του resmgr πριν από το destructor του cursor. Έτσι, όταν ο destructor του cursor πάει να καλέσει τη releaseTexture, το πρόγραμμα χτυπάει γιατί το map με τα counters δεν υπάρχει πια. Κακώς χρησιμοποιείς 3 map αντί για 1 map από tuple (ή αν το θέλεις πιο old school από κάποιο εσωτερικό struct). Όταν γράφεις ότι πρώτα καταστρέφεται ο resource manager και μετά ο cursor, αυτό σημαίνει ότι δηλώνεις πρώτα τον cursor και μετά τον resource manager: στη C++ τα αντικείμενα καταστρέφονται σε αντίστροφη σειρά από αυτή που δηλώνονται. Επομένως, είτε αντιστρέφεις τη σειρά που τους δηλώνεις, είτε τους δημιουργείς δυναμικά ως δείκτες, με new, οπότε ελέγχεις πλήρως πότε θα καταστραφεί ο καθένας. Αυτό είναι μια σωστή και άμεση λύση στο πρόβλημά σου. Αν το κάνεις, φρόντισε να υπάρχει comment που εξηγεί ότι η σειρά παίζει ρόλο. -------------------------------- Μια ακόμα καλύτερη κίνηση που θα έλυνε και το πρόβλημά σου "παρεμπιπτόντως" είναι το απλά να μην κάνει απολύτως τίποτα ο resource manager στον destructor του. Εφόσον προφανώς πρόκειται για singleton (αν όχι μηχανικά, τουλάχιστον λογικά) και εφόσον ξέρεις νομοτελειακά πως destruction του resource manager σημαίνει αυτόματα τερματισμός της εφαρμογής, δεν υπάρχει κανένας απολύτως λόγος να κάνεις τίποτα στον destructor. Update: βέβαια υπάρχει ένας "kind of" λόγος να κάνεις το destruction: το γεγονός πως αν δέν το κάνεις και αργότερα χρησιμοποιήσεις κάποιο utility για να κάνεις trace τίποτα memory ή resource leaks στο πρόγραμμά σου θα έχεις false positives (μιας και στην ουσία αυτά δεν είναι leaks αλλά το utility δεν το ξέρει). Επομένως το καλύτερο όλων θα ήταν να κάνεις π.χ. αυτό που λέει ο nplatis και αυτό που λέω παραπάνω: με μια #ifdef θα κανονίζεις ο destructor του RM να μην κάνει τίποτα εκτός κι αν έχεις ορίσει κάποιο macro (το οποίο θα το ορίσεις μόνο αν ποτέ κάνεις build του οποίου σκοπός είναι να κάνεις trace leaks). PS: Μου άρεσε το RAII που κάνεις με τους unique_ptr. Θα μπορούσες να γράψεις (μιας και δεν υπάρχει λογικά αυτή τη στιγμή διαθέσιμη) μια function make_unique της οποίας η δουλειά θα είναι αυτή που κάνει και η make_pair: να μη χρειάζεται να γράψεις τα template arguments.
Προτεινόμενες αναρτήσεις
Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε
Πρέπει να είστε μέλος για να αφήσετε σχόλιο
Δημιουργία λογαριασμού
Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!
Δημιουργία νέου λογαριασμούΣύνδεση
Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.
Συνδεθείτε τώρα