Tuesday, October 13, 2009

CccpKazRace


Over the past few days I finally ported JavaKazRace to C++, so it's now called CppKazRace 8)
Check the KazRace page, where I gathered a few infos as well as the link to the source code, which is now freely accessible.

I gave the Java source code to a few people before, but before being of any use, it clearly needed some refactoring. I don't envy those few that tried to make sense of the code as it was 8)

The code was written for speed under Java.. making me resort to using fixed point values here and there and also to use 1D arrays as 2D arrays, where in fact I wanted just arrays of structures.. but in Java a structure has to be an object that is dynamically allocated and I didn't want that !

The code clearly needed some major cleanup. Both logically and more general refactoring. Last time I looked into it I also didn't have any refactoring tools.
I think that nowadays refactoring is fairly well supported for Java. Nonetheless, I just decided to clean the code while moving to C++ and where I'm a lot more comfortable. Again Visual Assist X proved invaluable for the process of renaming tons of variables and methods.

It's not all being cleaned up (some members still use the "_member" notation versus the new "mMember" which I've been using recently), but the game mostly runs. There are some artifacts, mostly due to some memory not being cleared to 0 8)
Java clears all memory to 0 by default.. in C++ one has to do it manually..

Practically all dynamically allocated objects are kept with some sort of smart pointer facility that does delete the object when the smart pointer gets destructed.
Arrays use stl-like vectors and arrays of objects that own objects are overloads of the stl-like vector that deletes those objects when the vector gets destructed.

Memory management is a complex topic !
I don't like systems that do the garbage collection for me. It's very useful, but I think that it doesn't scale well for larger programs, where a lot of objects are allocated but they are also allocated with a certain hierarchy.

So, to create a texture object, I prefer:


Texture *pTex = pDevice->NewTexture()

..rather than..


Texture *pTex = new Texture();

This can also be achieved with C++ when using placement new.. which means running a constructor using your own memory area, but also means overloading the new to accept some sort of allocator parameter. So, in the case of the Texture object, the placement new should take a pDevice pointer.

It's not so much about having a custom allocator, but more having a concept of hierarchy. This is also important for multi-threading, where global resources are a pain by definition.


Anyhow.. zzzzz !

15 comments:

  1. So, to create a texture object, I prefer:
    Texture *pTex = pDevice->NewTexture()
    ..rather than..
    Texture *pTex = new Texture();

    Seems to me like you are moving towards that object factory concept bullshit.

    Anyway, what do you need constructor/destructor for? =) Why not just have Create() Destroy(), instead of relying on the c runtime to do stuff secretly behind your back!!!???

    ReplyDelete
  2. I don't think it's a "factory concept bullshit" thing ..it's very much how D3D works, where objects are created by a specific owner.
    I think a factory generally is about abstracting types rather than managing resources ownership.

    About using your own Create() and Destroy() in place of "new" and "delete". It's not so different if one uses a placement "new".
    If one is willing to use c++ exceptions, then it's more natural to use new/delete.

    I'm not the biggest C++ fan by any means, but minimal use of exceptions (just throwing the standard std::runtime_error) can simplify code a lot.
    Of course, when using exceptions one has to make sure that everything is handled the RAII way.. meaning that at the very least one should immediately wrap pointers into some sort of std::auto_ptr ..or any sort of template that will delete the pointer when leaving the function or when the owning class is destructed).

    ..I got used to that now and I'm glad I don't have to handle errors with return values, gotos and various escape sequences manually every time (I used to write a lot of explicit error handling code (^^;))

    ReplyDelete
  3. From the little experience I had working with exceptions seems like the only ones worth catching are the system/CLR ones (this was in C#), otherwise if you want to signal an error condition yourself, asserts do the job.

    ReplyDelete
  4. As far as the factory concept goes, what extra code do you need to have in pDevice->NewTexture() in addition to

    return new Texture(); ?

    ReplyDelete
  5. Asserts are mostly intended for well defined behavior. In fact usually they are for debug builds only.. because if things run fine once with the asserts on, then you don't need to check again.
    I like to keep my ASSERT() macro only for debug, but I also have an ASSTHROW() that will assert and throw in debug and simply throw in release mode.

    I use ASSERT() extensively for anything that doesn't change across different runs (mostly 8) and ASSTHROW() for anything that could be happening and from which I should recuperate.

    In a game on a console things are more defined. But once one starts dealing with network connections (which are unreliable by definition), or developing on PC.. especially something related to tools, or an engine build that must be able to deal with broken or missing data, then it's important to have proper and reliable error handling.. and that's where exceptions come handy 8)

    ReplyDelete
  6. About the non-factory thing.. 8)

    At the very least, the "device" should put the new texture's pointer in a list.
    The texture also gets a pointer to the device (its owner/manager), so that, when deleted, the destructor will ask to the device to be removed from the list.

    Being in the device's list is good to keep stats, also to reduce leaks, as a device shutting down would automatically destroy all objects that belong to it.

    If one has a reference counter for the objects, it would still be useful to see if some object has some dangling reference when its owner is destroyed.

    Also, deferring the creation to a specific owner/manager allows one to keep a per-owner pool of memory.

    moooo

    ReplyDelete
  7. Yeah, one of the big differences between a console engine and a PC is resource tracking. Things like shaders, textures, vertex/index buffers need to be cleaned up or sometimes rebuilt. At least in DX9 if you didnt rebuild them after a display mode change you sometimes got unpredicted results, even though I think DX was supposed to handle it for you.

    I remember now having to write a graphics resource manager that kept a centralized list of all D3D resources. We still managed to avoid having reference counters by splitting the resources into frames based on the engine logic.

    Common frame was the lowest and persisted through out the game, so any resources that belonged to it only got cleaned up when diplay mode changed or you exited the game. Then there was a Chapter and a Level frames that would get cleaned up in heirarchical order. 'Chapter' persisted across different levels, but was cleaned up when you went into main menu mode, 'Level' was per game level. You had to exercise some discipline when you were creating the resources to make sure they go into their appropriate frame.

    As far as exeption go for error handling, at Sega all the tools are c# and throw a lot of exceptions, but it seems like if they are non-fatal then people just tend to just ignore them.

    ReplyDelete
  8. Hmm ... why am I sensing seeing this on the iPhone/iPodTouch in the near future? Why? 8P

    ReplyDelete
  9. Paul,

    The project that you mention is a good practical example of what I meant.
    In general, as applications get larger, I think that having everything allocated flat in memory doesn't scale well. Structuring resources more may be a bit of a hassle.. but simplifies things in the long term, and helps to keep an eye on the situation.

    About exceptions.. some people's important errors are somebody else's annoying warnings.. that's a topic on its own.
    Regardless of that, from the programming point of view, I like the fact that I don't have to use return values to return error codes 8)
    If anything fails while loading a 3D model.. (a missing shader, a texture, etc.), with exceptions I can throw one with a message at the place of failure and then catch it again at a much higher level, where I can report the message and move to the next file to load.


    Ragin,
    eh eh eh !!! Didn't I rant against writing iPhone stuff some time ago ? 8) ..anyhow I'd like to try that indeed. But I fear having to mess with XCode and its crazy include paths, and also to learn all the platform specific stuff. Even just setting up a base framework is going to require some effort... not fun !!

    ReplyDelete
  10. He he he ... well, once it's in C++, the temptation to just see how it runs will be too great ... After this happens, you'll discover with just a little bit more work, you can add some extra tracks and sell it on the AppStore. 8P

    ReplyDelete
  11. Ragin.. it is in C++ already.. though it needs more cleanup and an OpenGL port.. but that should be fairly easy 8)
    The little more work kind of turns me off.. but we'll do something ! 8)

    ReplyDelete
  12. Ah, I just noticed that you've put up the source! Nice.
    The devil's always in those little details. 8P

    ReplyDelete
  13. Are we on Slashdot or something ? ;)

    ReplyDelete
  14. Hello Davide,
    Is it possible to download the java source code?

    Art

    ReplyDelete
  15. Hi Arturo !

    I think I meant to distribute the Java code.. I'll do that next.. I just need to put a proper readme file.

    Anyhow, the code is really ugly.. 8)

    ReplyDelete