• Advertisement

mr_jrt

Member
  • Content count

    179
  • Joined

  • Last visited

Community Reputation

134 Neutral

About mr_jrt

  • Rank
    Member
  1. Virtual object lifetimes

    Quote:Original post by Zahlman Quote:Original post by mr_jrt However, you're doing what I asked people not to do, comment on the method. I don't like making absolute statements like this, but for the sake of simplification: seemingly everyone makes this request, and it is almost universally wrong to do so. I can't comment for other cases, but as I explained above, I wasn't interested in why this code was bad (I know it probably is, it's an old hack-job that's slap-bang in the middle of a refactoring jobbie I'm doing on my code. In this case, the intent for the list is a prime candidate for some variety of observer pattern malarky). I just wanted to know the real technical reason why it occurs so I could be aware of this next time it occurs and/or avoid it occurring again. I'm not the kind of guy that likes to have the foundations of his understanding as "because thats the way it is".
  2. Virtual object lifetimes

    Firstly, all of you thanks. Special mention to the mighty Excors. All hail Excors! Thanks for proving I'm not going mad, or at the very least my installation isn't screwed up :) So, seemingly it could actually be a bug. Hmm. I wonder if in Microsoft towers someones cursing that they would've gotten away with it if it weren't for those damn kids....;) I may post to one of the C++ newsgroups and see what they say as to whether this is specified behavior or simply coincidental amoung the various compilers...if I can survive the burning sensation they'll induce from my mis-use of language features :) Quote:Original post by NotAYakk cModelBase -> cReferenceCountedObject & iModel cReferenceCountedObject -> iReferenceCountedObject iModel -> ??? (you didn't specify, but I'm guessing iReferenceCountedObject) cModel -> non-virtual cModelBase & iModel Quote:Original post by mr_jrt In the destructor of cModelBase, the this pointer is passed to cManager::unRegister(). This works correctly. At this point, you are dealing with a partially destroyed object. Yup. Quote:Original post by NotAYakk [url=http://www.artima.com/cppsource/nevercall.html]You should never call virtual functions during Construction or Destruction[/url], or cause them to be called. <snip> I'm betting that a class whose interface you are using is being destroyed on you before you call release(). The printf debugging will reveal this. Nope. As I said, it's still there in all it's reference-counted glory outside the function. As later proved by Excors. Quote:Original post by Fruny Quote:Original post by mr_jrt Prehaps lifetime was a bad title for the thread, it's possibly more accurately about the order of destruction of virtual base classes. C++ FAQ Lite 25.15 I was waiting for someone to bring up the Cpp FAQ entry :) Yes, I am fully aware of said facts (and was all but ready to admit defeat until Excors's VC2005 discoveries), but took the fact that the cModelBase was a) complete and b) the function being called was in a virtual base class and thus still present, to mean that it should work. Quote:Original post by Fruny Quote:Original post by mr_jrt Why is the object fully present from the outside, but not when inside a virtual function of a virtual base class when the object is being destroyed. Virtual base classes are destroyed last, after the whole non-virtual inheritance tree. Quote:Original post by mr_jrt The vtable must be intact, as the function is called correctly, but the virtual base object is not behaving as expected whilst inside the actual function. Once a derived class destructor has run, the object is no longer of that derived type. The vtable at that point will be that of the class currently being destroyed. <snip> Fruny, you seem to be trying to counter my code using an explanation that proves my points??? At the point where the cModel is only a cModelBase, the virtual base class is still in existance, as is cModelBase's vtable, this is why I took it to mean that things should work.
  3. Virtual object lifetimes

    Quote:Original post by Palidine wild guess: are you calling virtual methods in the base class constructor? the vtable is not set up yet for the derived class so that won't work. Nope, I am well aware of said situations. here's the code that is problematic ('Parent' is a reference to the manager object that created the model). My guess is that the use of the 'this' pointer in this context is the root of the problem. My guess is that I've created such a rare monstrosity that I've possibly hit a compiler bug. It wouldn't be the first time, I have a knack for finding these things it would seem. Quote:Original post by Enigma I think we're going to need to see some real code. It would also be helpful to know what compiler you're using. I tried to replicate your problem from the description you gave but was unable to. I'm using VC++.NET 2003. I don't see how the code will help any more than my description (bar of course, any coding mistakes by me ;)), but I'm game, so here are the relevant bits, edited for brevity... // From cModelBase.h... class cModelBase : public virtual cReferenceCountedObject, // Each object should only have one reference count public virtual iModel // Only one iModel interface should be present { public: virtual ~cModelBase() { Parent.UnregisterModelChild(this); } protected: cModelBase( iGraphics &NewParent ) : Parent(NewParent) {} private: iGraphics& Parent; // Reference to graphics parent }; // From cReferenceCountedObject.h... class cReferenceCountedObject : public virtual iReferenceCountedObject // Only one iReferenceCounted Interface should be present { public: virtual fastuint addReference(fastuint NewReferences); virtual const fastuint getReferenceCount() const; virtual fastuint releaseReference(); protected: cReferenceCountedObject( ) : References(1) {} virtual ~cReferenceCountedObject() {} private: fastuint References; }; // From cModel.h... class cModel : public cModelBase, // Base functionality virtual public iModel // Only one iModel interface should be present { // ... }; // From cGraphicsBase.cpp... (the base functionality of a iGraphics-compatible manager) bool cGraphicsBase::UnregisterModelChild(iModel * Model) { std::vector<iModel*>::iterator Result = std::find(ModelChildren.begin(), ModelChildren.end(), Model); if (Result != ModelChildren.end()) { std::cerr << "Model unregistered" << std::endl; (*Result)->releaseReference(); // <-- Here's where it all goes wrong! // Model->ReleaseReference(); // Functionally the same as above, but same result, just in case :) ModelChildren.erase(Result); return true; } else { std::cerr << "Model unregistration failed" << std::endl; return false; } } For what it's worth, I can post screendumps of the debugger illustrating this weirdness if need be.
  4. Virtual object lifetimes

    I appreciate you taking the time to reply. However, you're doing what I asked people not to do, comment on the method. I agree things could be better design-wise, but I want to understand the actual technical reason this strange event occurs. Prehaps lifetime was a bad title for the thread, it's possibly more accurately about the order of destruction of virtual base classes. Why is the object fully present from the outside, but not when inside a virtual function of a virtual base class when the object is being destroyed. The vtable must be intact, as the function is called correctly, but the virtual base object is not behaving as expected whilst inside the actual function.
  5. Here's an interesting one for you lot. I'm having trouble with the lifetimes of virtual base classes. I think. Here's a rough simplified analagy of what I'm working with. Interfaces: iReferenceCountedObject - Guess, I dare you. iModel - A generic model. Has a virtual iReferenceCountedObject base class, as all iModels have to support reference counting sematics. Classes: cReferenceCountedObject - Implementes common reference counting functionality. Has a virtual iReferenceCountedObject base class. cModelBase - Implements common functionality for models. Has virtual iReferenceCountedObject, iModel, and cReferenceCountedObject base classes. cModel - Implements all the functionality of a model. Has a cModelBase base class (and thus supports both the iReferencecountedObject and iModel interfaces). cManager - Keeps track of children. Has a createChild() that returns a new iModel instance, as well as register(iModel*), and unRegister(iModel*). Right, them's the classes. Here be thar problem. In cManager::createChild(), a cModel is created, and the iModel pointer is then registered to the list of children. In the destructor of cModelBase, the this pointer is passed to cManager::unRegister(). This works correctly. However, here's the strange bit. In unRegister(), I release() the iModel when I remove it from the register. If I examine the object in the debugger, it's all there (especially the vtable). When the release function is called however, we're in undefined territory. The release() code has the 'this' pointer in some random memory location with an invalid instance of the class. When the function returns, the instance is there again. A bad thunk it would seem. In the course of my debugging, I found that this problem only manifests when unRegister() is called from ~cModelBase(). My initial conclusion is that this is a lifetime issue, akin to the reason we don't call virtual functions in constructors/destructors. My understanding is that when unRegister() is called, the 'this' pointer is a cModelBase, not a cModel, the cModel having already been destroyed. It was my understanding that the virtual cReferencecountedObject still existed at this point (this seemingly being confirmed by the fact I can see the whole object clear as day except whilst in release() ), being part of the cModelBase and not the cModel, not to mention that as virtual base classes are constructed first, they should be destroyed last. I'm sure some of you might be tempted to tell me why doing things this way is wrong, but please resist. I want to know why *this* is behaving so strangely, not why it shouldn't be an issue :)
  6. I'm mentally experimenting with how I'm going to manage transparancy in my renderer, and I came up with a couple of questions. Due to the nature of transparancy, transparent polys are generally best rendered in a second pass. The method I'm warming to is the one where you render the opaque polys, then set the depth buffer to read only and render the sorted transparent polys. The main question I have is will non-transparent polys present in the second pass be a problem? (i.e. how granular does the transparent list of polys need to be, are we talking objects or polys). I suspect they won't, as although the depth buffer won't be updated, a transparent poly behind an opaque one won't show as I suspect OGL will just blindly render on top of the previous transparent poly, hence the need for the sorting to make sure you get the right results. Also, in a complex shape where there are likly to be overlapping forward-faces (hence, they pass the backface culling), will the poly that should end up behind the front one need to be sorted (I suspect it would). This means poly-level sorting as object level sorting won't be accurate enough. The easier solution to this is to simply have the transparent polys as seperate meshes so they can be easily rendered seperately, the problem with that is that I want to use RGBA textures for my alpha blending, so determining which polys have transparent bits is impractical as it will depend completely on the texture. So I'm now once again back where I started. unsure of how to accurately decide which polys go in the transparency list. So...any nuggets of experience anyone cares to share?
  7. OpenGL OpenGL 2D Operations

    Hi, cheers. I just sat and read through the FBO extention document linked to from an older thread (looks pretty much what I was after) before checking for replies to my thread. GameDev really could dp with a per-forum search option to save people (like me!) asking common questions that haven't quite made it into the FAQ yet, or use slightly different wording to that used in the FAQ. That said, any word on the support for FBOs on older hardware? As I'm designing library code, I want it to have fallback code paths for older hardware, and obviously, the less paths required the better, testing wise at least.
  8. I'm musing on a design for a graphics abstraction, one of the concepts of which requires various 2D primitives to be image operation targets. This rules out billboarded quads for these primitives as new textures will need to be generated each time they are rendered, and I would imagine that this is very inefficent using the standard texturing procedures, not to mention it would require all operations to be performed manually (i.e. losing the whole point of hardware alpha etc). What I need is a way to capture rendered output that is quick enough to perform several times a frame. Even though these are 2D operations, glRead/WritePixels et al are infamous as far as I'm aware for being far too slow for such heavily-used functionality, so I need to look into the use of textures to take advantage of the more optimised API features. I have an old DirectDraw implementation of this system that works quite nicely, but it's long overdue a overhaul to have a consistent 2D/3D system. Now I'm not too hot with OGL's finer points, but my el-quicko research suggests that my main options are: Rendering to pBuffers - according to nVidia, these are supported via extentions on anything newer than GeForces ...and for those without support for pBuffers (just to cover all the bases), the solution I found in NeHe (#36 I think ?) - Render to the viewport; Resize to target dimensions, render to backbuffer, copy results to a texture, then restore the viewport (can't imagine that being very fast though). Basically, what I'm after is comments as to the practicality of these options before I start designing things around them, let alone implementing them. I'll keep researching, but I'd appreciate any comments you may be able to offer.
  9. Just ignore that, found the problem. Double-deleting something that would take twice as long as the above to explain. Suffice to say, I'm feeling quite stupid. [headshake]
  10. [edit] Save yourself the trouble of reading this whole post, I just missed something obvious...[/edit] I've got some very strange shenanagains going on in my Visual Studio. Basically, here's a simplification of the classes involved (it's really much simpler than it looks, it's just somewhat verbose): a) iCaption is an interface that returns a string. (Used so generated strings can be used interchangably with strings when needed.) b) iPosition is an interface that shows the object has a postion and is movable. c) iFont is an interface that represents a text object that appears in the scene, it "is-a iPositional" d) iGameObject is an interface that represents a generic object that appears in the scene, it "is-a iPositional" e) cFont is just a concrete class that implements the iFont interface, and it "has-a iCaption" that it generates its text from f) cGameObject is just a concrete class that implements the iGameObject interface, and it "has-a cFont", 'theCaption', which it uses as a kind of label Also, all interfaces are inherited virtually. Ok, thems the classes, here's the problem. Everything works fine until it's time to clean up. As I delete these objects via pointers to their interfaces, virtual destructors are required. Things proceed as you'd expect (rough pseudo-ordering): delete Puck // (cGameObject*) = ~cPuck() ~cGameObject() { delete theCaption // (iFont*) = ~cFont() ~iFont() ~iPositional() } ~iPositional() ...if that makes any sense. The problem comes when 'theCaption' is deleted. It also "is-a iPositional", but for some reason (I'm guessing the compiler gets confused as both are "iPositional" the generated assembly jumps back into cGameObject's destructor and I get a stack overflow as it recurses ad infinitum. I suspect my abundance of virtual bits and bobs is the cause, but I'm fairly sure that the destructor of an aggregate class shouldn't be jumping around back into the destructor of it's containing class irregardless of how badly it's designed. I've re-built the whole project several times to no avail (just incase it was mixing stale object files), the dodgy code is always there. The only way to stop it happening that I've found is to delete 'theCaption' manually *before* I delete the cGameObject properly. A pain yes, but managable. Thoughts anyone as to whether it's a bug or this is the expected behaviour? (Visual C++ 2003, btw) - Jamie [Edited by - mr_jrt on August 17, 2005 8:19:47 PM]
  11. Following on your train of thought, I seem to have something workable by adding a bodge special case for dot(L,P)==0, where I then use std::numeric_limits<float>:min() instead of 0. This results in a +t and is thus sufficient to enable the collision response to occur workably. It's not the best solution, but it'll do until I'm a bit more capable with these problem domain. Thanks for your suggestions.
  12. For the purposes of simplicity the problem is the rigid-body interaction between a sphere and a plane, though the actual usage is a air hockey simulation. I noticed a prblem when the puck seemingly randomly (though intermittently) passed through the walls. I realised this was due to it being exactly the radial distance from the plane due to the rounding issues of floats, and as I checked for collisions using "if (t > 0) collide". I'm currently using the equation t = - ( ( dot(L,P) ) / ( dot(L,V) ) ) ...where: * L is the plane adjusted so its distance from the origin has had the radius of the sphere subtracted from it * P is the position of the sphere * V is the velocity of the sphere This gives t < 0 if the sphere is moving away form the plane, t > 0 if it is moving towards the plane, and 0 if it is moving parallel to it. Thus "if (t > 0)" determines if/when a collision will occur. The problem is that this doesn't account for one unique situation, when the bodies are in contact, but not actually penetrating; this also produces t=0, and is thus a problem. I attempted to work around this by setting the ignored parallel 't=0' to a random negative number, and changing the code to "if (t >= 0)"; this allows these collisions to be detected correctly. The second stage of simulation is of course moving the objects, but here lies my current problem. Now that I've determined that t=0 is valid, I am unable to use velocity correctly, as the usage requires: p2 = p1 + (v * t) ...which of course will result in a stationary object ad infinitum. I'm quite stuck here. Any ideas? I suspect that I need to prevent this situation from occuring rather than attempoting to deal with it when it does, but I'm unsure how to achieve that. Another thought is using a better integrator, as I'm only using a basic Euler at the moment given the simplicity of things, but as a consequence I cannot alter velocity without restarting the simulation frame. *Sigh* It's never simple, is it...
  13. I'm just looking for a quick confirmation, really. I'm working with a multithreaded Win32/OpenGL app, and I currently have the standard rendering context set as per normal. I have some loading in a thread and of course here lies the problem, as each thread requires a rendering context if it wants to use OpenGL functions. In itself this is no problem as new contexts are easily created, but as the thread is a loading thread, the textures and displa lists need to be availible to the main context. Enter wglShareLists()... Sadly though, it seems that wglShareLists() has a rather brain-dead limitation of requiring that the original context have no display lists (MSDN sayeth: "...The hglrc2 parameter should not contain any existing display lists when wglShareLists is called") before you can share the memory with another context (it errors with 0x000000AA aka. ERROR_BUSY aka. "The requested resource is in use"), making this rather useless for a dynamic system where contexts are created/destroyed as needed. So, back to the point. Is my grasp of the situation correct? i.e. am I going to have to create a pool of these shared contexts on startup and manage them as I do lights, or am I missing something obvious? [Edited by - mr_jrt on June 25, 2005 11:54:46 AM]
  • Advertisement