Jump to content


Member Since 24 Apr 2011
Offline Last Active Today, 07:34 AM

Topics I've Started

Class design, (kinda math) part two.

16 April 2013 - 10:45 PM

Hi folks, with a bit of time off traveling and an offshoot article about bit fields, time to get back to the plan I had.


In the original post about class design (link) I was just targeting a type of functionality which, if done poorly, can lead to trivial bugs that could have been avoided.  I think the responses were all valid and of course I made a couple changes, though I'm still on the fence about one of them because it hurts my world view and I have a different bit of thinking on it.  (Yeah, "Normalize" versus helper "Normalized".. :))


Anyway, stripping down the class in question, I ended up with the following (code/comment stripped to get an idea of how 'self documenting it is' and raw feedback before I make comments below and hit the 'reasons'):


class Vector3fv
  static const size_t kXIndex = 0;
  static const size_t kYIndex = 1;
  static const size_t kZIndex = 2;
  Vector3fv( const Vector3fv& rhs );
  Vector3fv( float x, float y, float z );
  Vector3fv( SIMD::Vec4f_t rhs );
  operator SIMD::Vec4f_t();
  float X() const;
  float Y() const;
  float Z() const;
  void X( float x );
  void Y( float y );
  void Z( float z );
  float operator ()( size_t index ) const;
  void operator ()( size_t index, float v );
  void Set( float x, float y, float z );
  const Vector3fv& operator =( const Vector3fv& v );
  Vector3fv operator -() const;
  Vector3fv operator +( const Vector3fv& rhs ) const;
  Vector3fv operator -( const Vector3fv& rhs ) const;
  Vector3fv operator *( float rhs ) const;
  Vector3fv operator /( float rhs ) const;
  float operator *( const Vector3fv& rhs ) const;
  void operator -=( const Vector3fv& rhs );
  void operator +=( const Vector3fv& rhs );
  void operator *=( const float rhs );
  void operator /=( const float rhs );
  void Normalize();
  void Cross( const Vector3fv& rhs );
  static const Vector3fv kZero;
  static const Vector3fv kXAxis;
  static const Vector3fv kYAxis;
  static const Vector3fv kZAxis;
  SIMD::Vec4f_t mData;
inline float Dot( const Vector3fv& lhs, const Vector3fv& rhs );
inline Vector3fv Normalize( const Vector3fv& rhs );
inline Vector3fv Cross( const Vector3fv& lhs, const Vector3fv& rhs );
inline float Magnitude( const Vector3fv& lhs, const Vector3fv& rhs );
inline Vector3fv operator *( float lhs, const Vector3fv& rhs );

Writing articles is good for "YOU".

10 April 2013 - 01:14 AM

An old saying, which is very true: If you can't explain something then you don't really understand it yourself.


I've finished up, for the time being, a series of articles on how to build a cross platform build environment.  You've likely seen them, the big "CMake" labelled things.  As my first real effort writing a series of articles outside of internal documents, I think they went over (are going over) fairly well.  I knew folks liked my documentation at work in general and there were no horrible complaints.  So transitioning to writing publicly with more than say 20 readers, I feel pretty happy with the results.


But, the thing I forgot about before writing up articles, is how good it is for me, personally, to go through all the details and describe them in a proper manner.  If you think you know something 'well', write an article and find out how much you generally 'gloss over' in your day to day use.  For my set of articles I actually had to clarify my usage against actually understanding the real workings of certain things.  I used things like the dependent options but I rarely really considered what I was doing with them until I had to describe it in plain English.  Putting the usage into plain English forced me to really think about why I was doing things in a certain way which actually led to re-evaluating a lot of things I do lately.


If folks didn't consider me knowledgeable about CMake prior to the articles, I think they may consider me that way now.  But more importantly, I know I am fairly knowledgeable in that I can clarify details based on anything described in the article since I had to deep dive and make sure I really understood the features in order to describe them.  (NOTE: I could have written things correctly and *STILL* misunderstood some minor details, it is possible and I accept it.)  In saying this I suggest leaving the term "expert" out of your vocabulary.  There is no such thing as expert, very very knowledgeable yes, but expert semi-implies infallible.  As programmers we all know that is not realistic.


Edited for clarity since this is now linked from the front page.. (EVIL Michael! :))

Class design, kinda math specific.

31 March 2013 - 03:46 PM

Hi folks, continuing on with re-evaluating a bunch of different things in code as preparation to write another article, a fun one came up which actually someone asked a related question about involving statics.  While the question was just "why" you would use it, it applies to the bit I'm considering at this moment.  This may be better suited for the math forum's as it is related to math classes but it really is a general programming topic in terms of good class design.  Anyway, the basic idea is picking on a single function in a 2d or 3d vector class and considering the ups/downs and possible use cases for the function.  While not absolutely critical, I've been re-evaluating it in regards to C++11 (not much change) and common bug scenarios, plenty.  The function is simple: "Normalize".


OK, many may be asking just how complicated could "Normalize" be to write.  Well, surprisingly complicated if you want to avoid the most likely set of bug producing use cases.  I normally re-evaluate how I code every couple years in order to break bad habits I've fallen into (hence the question about includes using brackets versus quotes, seems I'm doing OK in reality) and to see if I've learned anything over that time which could be applied and made better.  (Hopefully forming a new habit.)  In this case I was working up the preliminaries to extending on the articles I've been writing by attacking a cross platform vectorized math library using SSE and Neon as targets with a fallback to non-vectorized code as required.  (I.e. SSE is pretty much a given anymore, Neon is most definitely not.)


Anyway, let's produce some use cases and questionable results:



Vector3f  toTarget = target - source;
Vector3f  upVector = Vector3f::kYAxis;
Vector3f  rightVector = toTarget.Normalize() ^ upVector;


Make two assumptions from here out please: toTarget is not nearly equal to upVector and unit vector is give or take a certain amount of error around 1.0f.  Otherwise I'll be describing possible edge cases more than getting to the point...  smile.png


Ok, so assuming I got the cross product order correct (I ALWAYS forget... smile.png) I should end up with unit vectors of up and right.  Some libraries I've worked with would have a side effect, toTarget would have been normalized in that code.  I despise side effects so any behavior which ends up with toTarget being normalized is out the door, so if I want this code to work as written the signature has to be: "Vector3f Normalize() const".  It could be non-const but that defeats self documentation in saying *this* is not changed after the call, so non const is also out the door.


Second use case is a two parter.



Vector3f incidentVector = -toTarget.Normalize();


Again, no side effects allowed so the above change works and we get the negated unit vector as desired.  Now, in "documenting" code I often break up equations, this would not normally be a candidate but assuming this were some big nasty equation with lots going on that needs to be explaining, I may break the code into the following pieces:




Vector3f incidentVector = -toTarget;
Do you see the bug?  I have done this sort of thing many times over the years and seen many others do the same thing.  The normalize call no longer "does" anything since it is const and not modifying "this" without an assignment is a nop.  Some compilers will give you warnings in this case and prevent you hunting the bug, but not all notice it.  Not to mention a lot of code bases turn off "unused return value" warnings since there are a pretty numerous set of cases where it is perfectly valid to ignore return values.
This leads to two conflicting desires, one is to maintain easy to use code and the other is to prevent silly errors such as case two.
So, I was thinking I'd use the following signatures:
class Vector3f
   void  Normalize();
   friend Vector3f Normalize( const Vector3f& );  // Optional, could just have the helper
  // defined as: { Vector3f result = in; result.Normalize(); return result; }  which most
  // optimizers with do well with.  (NOTE: C++11 I believe the return would be a &&,
  /// still exploring that bit in detail as part of my re-evaluation..)
Given this you have to modify the first use case to use "Normalize( toVector )" since there is no return to work on from the member function.  The second use case works as intended and the easy to refactor into a bug issue goes away.
Now, where all this long winded discussion of a single function ends is: "what am I missing"?  I've fixed a recurring problem and don't see any further problems which the compiler won't catch and error on.  But, I might be too deep in the weeds of one variation to consider the "other" use cases which would lead this to producing yet other common mistakes.  I've tried but can't see any real downfalls other than annoying folks used to the more common signatures.
I don't expect a perfect answer, I just don't want to give a poor one if I write an article including this.  smile.png
OOPS: Forgot to mention why I was considering the static variation, instead of a helper I was possibly considering using a static "static void Normalize( Vector& )" instead of the void return member.  I.e. "Vector::Normalize( toVector )" as an alternative.  I think the member+helper works better without the extra typing.

User versus System include, quotes and brackets etc.

29 March 2013 - 12:12 PM

In the process of writing a series of articles I realized that I stopped using the preprocessor differentiation of user and system paths in the "technically" correct manner.  Part of this is on purpose but part is basically because I don't find the differentiation particularly useful anymore.  Anyway, the question is how you use includes and how much you use the different path rules and why?



With Gcc/Clang you have -I and -iquote options to control the different paths between quoted and bracketed.  As far as I can remember, Cl (Visual Studio) supplies no '-iquote' style variation so the only difference is the search of the local or relative directories between brackets and quotes.


My thinking is that, obviously there is a notable difference and I do use it correctly in most cases I believe.  '#include "item"' looks in the current directory first, so unless the current code location has been added to the search path, this is how you get at local headers.  For almost every other usage though, I rely on controlling the include path order and using naming/nesting habits which avoid conflicts.  Someone ran into conflicts recently with 'math.h' which in conjunction with proofing the code made me think about this problem and how I had been writing code to avoid it.


Other than local, or relative to the current directory, searches, what other reasons do folks use the quoted versions for?  I realized my example code needed a fix since my reasons for using quotes and brackets didn't match what I put in the example code.  Oops.  But as stated, it got me to thinking about this and how others deal with it.  I might even turn it into a little article for fun. smile.png

To reflect or not to reflect.

02 November 2011 - 08:04 PM

So, in the process of finishing off a new variation of my object system it came time to integrate it with tools and editors. This is always such a fun item, given that with C++ the obvious answers available in other languages are not so "obvious", i.e. reflection used to generate the UI's as property sheets etc on the fly. I've used several reflection systems for C++ mostly with mixed results. Some were a massive pain for maintenance reasons such as requiring external tools to generate reflection headers and of course any circular dependency requires two rebuild all's if they resolve at all and of course it was a nightmare to track down some of the problems. Other reflection systems for C++ require manual description in cpp files, not a horrible thing and this is the way I'm leaning once again, but not a single one I've found has the ability to be compiled out for a final release build and some of them add significant chunks of dead weight to the final product. An example, when working on PS2 game, a "properties" system for the editor added nearly 3 megs to an already huge (relatively speaking) executable, very bad news on a limited memory system.

At the same time, there are some reasons to keep "parts" of the system in place. All of the UI items such as descriptions, tool tips, editor types, etc can go away. But some things such as the properties and their types are very useful if you utilize a scripting system. You can kill two birds with one stone and integrate scripting at the same time you are describing the code to the editor. And of course, the root of the system, which I use is a RTTI implementation which I use to replace the limited C++ rtti abilities, so that gets to stay also. (NOTE: I say limited because I attach some additional attributes to describe the class for various reasons used in the engine which C++ rtti doesn't allow.)

Another item to consider, I don't want any non-stack allocation taking place before main enters and gets all the memory systems redirected and setup the way I want them (if at all possible of course). This is not a "critical" point but I'd very much like all the overhead of the RTTI tracked along with everything else. Since it exists from startup to shutdown, it is not a big issue, I just prefer to not miss anything if possible.

Ok, all the rambling explanation aside, here's the current thinking and initial working code examples:

1. Use an initializer chain based system. Even if there are memory allocations off of the stack they will be released prior to entering main. So the idea is basically:
static RttiInfo XXXXClassRttiInfo = RttiDef::Begin( "Someclass" )
  .Property< uint32_t >( "Blargo" )
   .Get< uint32_t >( &Someclass::Blargo )
   .Set< uint32_t >( &Someclass::SetBlargo )
   .Description( "Some uint32_t." )
It is verbose (can be wrapped with templates/macro's to simplify most of this to single lines) but the idea is intended to deal with all the items mentioned above. The stack based temporary which "Property" returns simply records the given information in local variables and the "End" puts those variables into the RttiDef which "Begin" put on the stack. In the case of "Description" in a retail build it just returns without doing anything and the string given it "should" be stripped as unreferenced by the linker.

2. The descriptive ability is easily extended in many ways depending on what the tools require/desire and things such as description just compile out to nothing in retail. Min/Max values for a control, type of editor to present, order of controls, groups of controls, whatever you may want.

3. With a bit of work all the remaining retail data can be serialized in the tools and saved with optimized level/area/whatever systems.

So, to do "everything" mentioned is a pretty large undertaking but to get what I want/need right now, I implemented just the editable property system in fairly short order and it compiles out of retail nicely. What I'm looking for here is alternatives, suggestions, etc which fit the desired requirements or are close enough to justify some hacking. A swift kick to the head for missing some library or alternative would be welcome. :)