fastest 3D software for linux

Linux 3D
interactive ray-tracing
[Introduction] [Overview] [Coding Standards] [Core reference] [3D reference] [GUI]

Equinox3D C coding standards

Contents

  • Indenting
  • Names
  • Variables
  • Pointers
  • Prefixes
  • Switch / case
  • Debug-code
  • Conditional compilation


  • Indenting

    Use tabs for indenting:
  • Some folks like large indenting steps, some of us prefer small steps. Using tabs allows everyone to set the appearance locally without modifying the file!
  • Simply set the tab size to your liking in your code editor. This prevents those annoying indenting changes that pollute the repo's change history.
  • The ASCII standard has a dedicated character for indenting (tab). Use it!
  • It leads to smaller files.

  • After the first non-whitespace character, use spaces for aligning, so tab-size changes move things smoothly.
      int    LVar = 0;
      float  LNumber = 42.0;
    ^      ^^
    tab    spaces
    
    
    Put the opening bracket for "if", "for", "while" etc. in a separate line, for better readability:
      if(LMesh0 == LMesh1)
      {
        if(LThis > LThat)
        {
        }
        else if(LThis < LThat)
        {
        }
        else
        {
        }
      }
    

    Names

    Use descriptive names. Remember that someone will need to understand your code some day.

    Good:
    E3dMesh*  LBaseMesh;
    E3dMesh*  LCurrentLevelMesh;
    
    Bad:
    E3dMesh*  m;
    E3dMesh*  n;
    
    In 6 months, even you might not remember what n and m mean.

    Use upper-camel-case names for types, variables and functions, as it allows for consistency.

    Example:
    E3dMesh_PolyGroupAdd()
    This guarantees that the 'M' in 'Mesh' is always capitalized, allowing for case-sensitive searches.

    Avoid lower-camel-case because it creates a mess.
    It would make the 'm' in 'Mesh' sometimes upper case, sometimes lower case:
    Example:
    startMeshIterate()  // 'Mesh' is capitalized
    meshIterate()       // And, now, it isn't. Can't do a case-sensitive search!
    

    Variables

    Practice good variable 'hygene':
  • Declare variables at the tightest-possible scope to avoid side-effects.
  • Use 'const' generously.
  • // We 'guarantee' that PMesh and PCount will not be changed by this function (multi-threading friendly)
    static void _SomeFunc(const E3dMesh* PMesh, const EIndex PCount)
    {
      if(PCount > 0)
      {
        int  LX = 0;  // Define here, not at the top of the function, so other branches can't see it
    ...
      }
      else
      {
      }
    }
    

    Pointers

    Pointer types should have the '*' right after the base type because it is part of the type.
    Proof: You can do this in C, so the '*' is clearly part of the type:
    typedef int*  IntPtr;
    Good:
    int*  LVar0 = NULL;int*  LVar1 = NULL;
    
    typedef int*  IntPtr;
    IntPtr  LVar0 = NULL, LVar1 = NULL;
    Bad / confusing:
    int  *LVar0 = NULL, *LVar1 = NULL, LVar2 = 7;
    We consider the C standard allowing this, as a "bug".


    Prefixes

    Prefixes are used for name spaces to make global searches easy.
    For example:
    E3d library types look like this:
    E3dMesh, E3dPolyGroup
    Image ("picture") library types use the 'Ep' prefix:
    EpImage, EpPixelBuffer, EpToneMapper
    Types are used as function name prefixes, before an "_'.
    Examples:
    E3dMesh_PolyGroupAdd(E3dMesh* PMesh)
    EpImage_Resize(EpImage* PImage, ...)
    This makes things familiar to C++ folks:
    E3dMesh::PolyGroupAdd()
    EpImage::Resize()
    Function parameters are prefixed with a 'P' and local variables in functions are prefixed with an 'L'.
    This makes it easy to tell them apart with a quick glance:
    void E3dEdge* E3dMesh_EdgesAdd(E3dMesh* PMesh, const EIndex PCount, const EBool PInitialize)
    {
      E3dEdge*  LNewEdges = NULL;
      EIndex  LI = 0;for(;LI < LN;LI++)
      {
        if(PMesh->Vertices.Count > 0)  // I can immediately tell that PMesh is a function parameter and not a local or a global variable
        {
        }
    ...
      }
      return(LNewEdges);
    }
    
    Macro function parameters and local variables are prefixed with a lower-case "p" a lower-case 'l', respectively. This helps avoid clashes with parameters and local varialbles of "real" functions.
    The use of "do" and "while(0)" are encouraged for macros, as the "while(0)" "swallows" the semicolon when calling the macro, so macro function calls are consistent with regular function calls:
    #define E3dMesh_SomeMacroFunc(pOldMesh) do\
    {\
      int  lSomeCounter = 0;\
      E3dMesh*  lNewMesh = NULL;\
    } while(0)
    
    ...
    
    E3dMesh*  LMesh = LSomeMesh;
    E3dMesh_SomeMacroFunc(LMesh);   // <-- ';' is swallowed by the 'while(0)' in the definition of E3dMesh_SomeMacroFunc
    
    Global variables should be avoided, if possible, but if used, they should have some namespace prefix, to minimize clashes. Example:
    EIndex  E3d_MaxNumOfSomething = 42;
    Static global variables and function names should start with an '_'
    static int  _SomeVariable = 42;
    static void _SomeLocalFunction(void);
    

    Comments

    Do not indent comments. This allows for more room without breaking into several lines when inside a deep inner scope.
      if()
      {
         if()
         {
           for()
           {
    // Starting the comment here leaves more room and it's easier on the eye for reading through multiple comments.
             // Versus starting here.
             if()
             {
               // Then here. Now, your eye has to jump horizontally from comment to comment...
               if()
               {
               }
             }
           }
         }
      }
    

    Loops

    Define and initialize loop variables before the 'for' statement like this. It allows for multiple types:
      {
    // All loop variables are declared and initialized right here:
        EIndex  LI = 0;E3dVertex*  LSrcVertex = LMesh->Vertices.Items;E3dCoord3*  LPosition = LPositions;for(;LI < LN;LI++)
        {
        }
      }
    
    C allows for this, but it's a half-baked solution because it only allows for one type ('int', in this case) and often, multiple types are needed.
    for(int LI = 0:LI < 3;LI)

    Switch / case

    Format switch / case statements like this for good readability:
      switch{LVariable)
      {
        case 0:
          {  // Need a '{' because we are declaring variables
            int  LBlah = 72;
    ...
          }
        break;
    
        case 1:
    ...
        return(42);
    
        default:  // Always have a 'default'. It's also good practice to catch unhandled defaults with a warning
    if(DEBUG) { printf("%s() %s:%d  %sIMPLEMENT ME%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeYellow, ECodeNormal);fflush(stdout); }
        break;
      }
    

    Debug-code

    Temporary debug-code is allowed. It should not be indented, so we can easily identify it. Use conditionals to keep console output noise-free.
    if(LMesh0 == LMesh1)
    {
      if(LThis > LThat)
      {
      }
    // Debug
    if(E3dDebugSOME_CRITERIA) { printf("%s() %s:%d  %sDebug printf%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout); }
      }
    }
    

    Conditional compilation

    When possible, use 'real' conditionals, even when using #define for conditional compilation. This ensures that the code path is parsed by the compiler, which helps to prevent "code rot":
    #define E3dPROTOTYPE_MESH_CODE  0
    ...
    
    Good:
      if(E3dPROTOTYPE_MESH_CODE)
      {
    ...
      }
    
    Bad, allows for code rot:
    #if E3dPROTOTYPE_MESH_CODE
    ...
    #endig
    
    The compiler will not generate machine code for either case, but in the first case, it will parse the inner code, so it will guarantee that the unused code will always compile.
    Only use the latter form if it's necessary. E.g. when declaring variables conditionally, or differently, based on the #define.

    © 1996-2025 By Gabor Nagy