Having dipped my toes in the scandalous, muddy waters of multiple inheritance (one structure with two parents, which in programming is bad – all the cool structs and classes have just one direct ancestor), today I decided to adventure into the realm of operator new overloading…
We have some very large class definitions – which are iteratively refactored C structures that got renamed “class” when I started slapping member functions into them, eventually grew constructors and destructors and have generaly slid into C++ishness.
Originally they were created on demand with calls to calloc() so they were nice, shiny and all set to zero everywhere; or they were allocated in large blocks and tossed onto a “free list” to save memory fragmentation and the overhead of a call to calloc (you coders do realize that malloc, calloc, new, etc, all internally use a mutex in a multi-threaded app, right?) and the function that pulled a free member would memset the result for you. Shiny!
In the first iterations of class transition, that was OK. But eventually we started adding STL members into these classes, and they do not like to be memset after construction. And if your struct has a handful of arrays and other fiddly members, maintaining the constructors becomes a really annoying exercise in repetition (or copy & paste if you get too lazy).
I mean, it’s really nice to know that every last byte of my structure was assigned the proper variant of ‘0’ by a a verbosely written exacting assignment specification. But I’d rather just be able to say “all zero”.
So since this is partly a performance consideration, and since I was trying to avoid a lot of rework at the time, I settled on creating a CSAFE_Player structure for each Player class. These structures contained all the simple members of the Player structure, allowing them to be memset nice and cleanly in the constructor for the fancier C++ class.
If I actually stopped and rewrote all of these structs, I could break them up into nicely compartmentalized objects with cleanly defined interfaces – but I’d have to rewrite almost the entire of each host process to support such a change.
So what we have right now is big structs classes that internally cross-reference all sorts of bits of themselves through relatively long lists of member functions (or, worse, through non-member functions that haven’t been absorbed into the classes yet, although I am slowly refactoring members into private and protected space). Except they are derived from a struct CSAFE_Player which itself derives from wwii::Player.
Reason we’re in this state:
- Throw it all out and start over :- not an option
- memset()ing the bulk of the struct is an efficiency consideration
- can’t memset the entire structure because STL members get antsy
- the 2-3 constructors per class to maintain all the members is a pain and you can’t initialize the array members the same way as non-arrays
- wwii::Player is a new addition that standardizes some of the data previously in the CSAFE_Players and Player struct/classes to a standardized base class
The biggest problem with this is that I wwii::Player can’t be a nice base class with virtual members, the CSAFE_Player deriving it is guaranteed to whack the vtables. That would be so not good.
So the option of doing something about it comes into scope (as long as I can do something about it in short order).
Considering all the options over night, my conclusion was that if I overload operator new in wwii::Player to allocate batches of blank slates and perform its own freepool system to avoid fragmentation etc, then the memset will have occurred before the constructor, meaning that the constructor can start with a carte blanche and things are good.
My new adds some overhead to each allocation – I have to add a few bytes to tell myself how big this particular object was (since it could be either a wwii::Player or a Player or a derived class such as IRCPlayer on the chat host). I round the size up to the nearest 1kb and put each 1kb size into buckets. Each time a bucket gets empty I calloc() another 32 and split them up into the free pool. The is payoff in the grouping together of allocations.
I started a little hesitantly, but 3 iterations of refactoring and code-reviewing (taking care to back trace from where my changes impacted to see if there were obvious lurking) and I’d made quite nice progress. The pan-host instancing functionality is librarized and the relevant hosts link against it; a slew of replicated code is transitioned to the wwii::Player structure and the map/cell construction of a wwii::Player from a MissionPacketXfer is described in a precise constructor in wwii::Player. The instancing code itself is integrated into the common Player class such that it becomes automatic across the hosts.
A whole bunch of stuff is tagged for further instancing integration — each host runs its own set of side counters which have always been a little problematic, but now I can just use the instancing systems’s counters and only have to write the code once.
Finally I was able to get to the meat and potatoes; until the next iteration, a vehicle becomes instanced as the pilot attaches to it (multi-crew support), and vehicles can only see other vehicles in their own instance.
It came together incredibly fast at the end and was up and running in no time. I would have liked to have spent some time adding some unit tests for some of the refactored code, it far better lends itself to it, but I was rather tired and let my coders instinct that writing tests costs time win over.
I had time to run the host through a series of basic tests and code coverage seemed to indicate I ran through all but some of the more esoteric error handling lines of my code. There’s still quite a few iterations to go through but I think I’m finally over the biggest round of obstacles. Now I have to go through and lock down various things to be instance specific — e.g the in-game clock, EWS, AWS, various chat channels, etc.
Looks like my Saturday will be spent working with Ramp testing his tutorials and delivering him the interface for tutorial tracking (Chinese players will have to complete basic training tutorials and one-or-more multi-player tutorials before gaining access to campaigns).
While it’s taken some “naughty” coding (multiple-inheretance, operator new overloading) this isn’t sweeping the badness under the rug – I’ve actually eliminated far more serious crimes against coder kind. I’m pretty pleased with this week’s progress.