views:

53

answers:

2

Hello coders out there!

Currently I want to optimize my 3d engine for consoles a bit. More precisely I want to be more cache friendly and align my structures more data oriented, but also want to keep my nice user interface.

For example:

bool Init()
{
  // Create a node
  ISceneNode* pNode = GetSystem()->GetSceneManager()->AddNode("viewerNode");

  // Create a transform component
  ITransform* pTrans = m_pNode->CreateTransform("trans");
  pTrans->SetTranslation(0,1.0f,-4.0f);
  pTrans->SetRotation(0,0,0);

  // Create a camera component
  ICamera* pCam = m_pNode->CreateCamera("cam", pTrans);
  pCam->LookAt(Math::Vec3d(0,0,0));

  // And so on...
}

So the user can work with interface pointers in his code.

BUT
In my engine I currently store pointers to scene nodes.

boost::ptr_vector<SceneNode> m_nodes

So in data oriented design it's good practice to have structs of arrays and not arrays of structs. So my node gets from...

class SceneNode
{
private:
  Math::Vec3d m_pos;
};

std::vector<SceneNode> m_nodes;

to this...

class SceneNodes
{
  std::vector<std::string> m_names;
  std::vector<Math::Vec3d> m_positions;
  // and so on...
};

So I see two problems here if I want to apply DOP. Firstly how could I keep my nice user interface without having the user to work with IDs, indexes and so on?

Secondly how do I handle relocations of properties when some vectors resize without letting users interface pointers point to nirvana?

Currently my idea is to implement a kind of handle_vector from which you get a handle for persistent "pointers":

typedef handle<ISceneNodeData> SceneNodeHandle;
SceneNodeHandle nodeHandle = nodeHandleVector.get_handle(idx);

So when the intern std::vector resizes, it updates its handles. A "handle" stores a pointer to the actual object and the "->" operator is overloaded to achive a nice wrapping. But this approach sounds a bis complicated to me?!

What do you think? How to keep a nice interface, but keep thinks contiguous in memory for better cache usage?

Thanks for any help!

+1  A: 

You will need to use smarter handles than raw pointers. There is no way around it with DOP.

This means:

class SceneNode
{
public:
  std::string const& getName() const { mManager->getSceneName(mId); }
  void setName(std::string const& name) { mManager->setSceneName(mId, name); }

  // similar with other data
private:
  ISceneManager* mManager;
  size_t mId;
};

One very good point though: the user cannot accidently call delete on one of the pointer you returned now. That's why smart handles are always better.

On the other hand: how are you going to deal with the lifetime of the pointee of mManager is another issue :-)

Matthieu M.
Yes I also thought in this direction, but it doesn't seem very elegant to me... But I think I will choose such a solution and hide the indexing from the user. In my design all "managers" are global. So a simple assert() in debug mode to ensure we have a valid manager is enought. This is a big drawback of DOP. Thank you!
VitaminCpp
@VitaminCpp: it's not really a drawback, as far as I am concerned, because I always use smart handles anyway when designing an interface. Much easier to evolve, may include reference counting transparently, protects the content and may provide binary compatibility much more easily.
Matthieu M.
Indeed this is a strong argument! Now the hard part is to remodel the scene node itself DOP... :( Thanks a lot!
VitaminCpp
A: 

For those interested in a practical example of DOP, have a look at this fantastic presentation from Niklas Frykholm => http://bitsquid.blogspot.com/2010/05/practical-examples-in-data-oriented.html

This helped me to implement my scene graph in a data oriented manner.

VitaminCpp