views:

378

answers:

8

This is in C++.

So, I'm starting from scratch writing a game engine for fun and learning from the ground up. One of the ideas I want to implement is to have game object state (a struct) be double-buffered. For instance, I can have subsystems updating the new game object data while a render thread is rendering from the old data by guaranteeing there is a consistent state stored within the game object (the data from last time). After rendering of old and updating of new is finished, I can swap buffers and do it again.

Question is, what's a good forward-looking and generic OOP way to expose this to my classes while trying to hide implementation details as much as possible? Would like to know your thoughts and considerations.

I was thinking operator overloading could be used, but how do I overload assign for a templated class's member within my buffer class?

for instance, I think this is an example of what I want:

doublebuffer<Vector3> data;
data.x=5; //would write to the member x within the new buffer
int a=data.x; //would read from the old buffer's x member
data.x+=1; //I guess this shouldn't be allowed

If this is possible, I could choose to enable or disable double-buffering structs without changing much code.

This is what I was considering:

template <class T>
class doublebuffer{
    T T1;
    T T2;
    T * current=T1;
    T * old=T2;
public:
    doublebuffer();
    ~doublebuffer();
    void swap();
    operator=()?...
};

and a game object would be like this:

struct MyObjectData{
    int x;
    float afloat;
}

class MyObject: public Node {
    doublebuffer<MyObjectData> data;

    functions...
}

What I have right now is functions that return pointers to the old and new buffer, and I guess any classes that use them have to be aware of this. Is there a better way?

A: 

I believe both DirectX and OpenGL support this natively. Since the graphic card's swapping will be much faster than yours, you should use theirs whenever possible.

BlueRaja - Danny Pflughoeft
He is not talking about double buffering graphics, he is talking about double buffering game state.
Chris
ah, not talking about graphics. Talking about game state data. The idea is, I can have a render thread rendering from old data concurrently with other threads updating the game world. Graphics double buffering is a little different.
Gary
+5  A: 

I wouldn't do anything 'clever' with operator overloading if I were you. Use it for completely unsurprising stuff which is as close as possible to what the native operator would do, and nothing else.

Not really clear that your scheme particularly helps with multiple writing threads anyway - how do you know which one 'wins' when several threads read old state and write to the same new state, overwriting any earlier writes?

But if it is a useful technique in your app, then I'd have 'GetOldState' and 'GetNewState' methods which make it completely clear what's going on.

Will Dean
thanks, yea, multiple writing threads I'll have to solve later. I just want to make it easy when I have stuff like crazy physics going on, but wouldn't that problem be independent of this data organization?
Gary
+2  A: 

I'm not sure that having effectively two states is going to mean you don't need any synchronization when accessing the writable state if you have multiple threads writing, but...

I think the following is a simple and obvious (to maintain and understand) pattern you could use with little overhead.

class MyRealState {
  int data1;
  ... etc

  protected:
      void copyFrom(MyRealState other) { data1 = other.data1; }

  public:
      virtual int getData1() { return data1; }
      virtual void setData1(int d) { data1 = d; }
}

class DoubleBufferedState : public MyRealState {
  MyRealState readOnly;
  MyRealState writable;

  public:
      // some sensible constructor

      // deref all basic getters to readOnly
      int getData1() { return readOnly.getData1(); }

      // if you really need to know value as changed by others
      int getWritableData1() { return writable.getData1(); }

      // writes always go to the correct one
      void setData1(int d) { writable.setData1(d); }

      void swap() { readOnly.copyFrom(writable); }
      MyRealState getReadOnly() { return readOnly; }
}

Basically I've done something similar to your suggestion but using overloading. If you want to be careful/paranoid I'd have an empty class with virtual getter/setter methods as the base class rather than as above so compiler keeps the code correct.

This gives you a readOnly version of the state which will only ever change when you call swap and a clean interface whereby the caller can ignore the double buffer issue when dealing with the state (everything that doesn't need knowledge of old and new states can deal with MyRealState "interface") or you can downcast/require the DoubleBufferedState interface if you care about before and after states (which is likely imho).

Clean code is more likely to be understood (by everyone including you) and easier to test, so I'd steer clear of operator overloading personally.

Sorry for any c++ syntax errors, I'm a bit of a java person now.

DaveC
Thank you, I think something like this is what I'm looking for, maybe to save some repetition, I can use templated inheritance from all my various state objects.
Gary
still, the issue I have with this is I want a generic way to do this. All my data structs might not necessarily have a getdata1() function.
Gary
+4  A: 

I recently dealt with a similar desire in a generalized way by "snapshotting" a data structure that used Copy-On-Write under the hood. An aspect I like of this strategy is that you can make many snapshots if you need them, or just have one at a time to get your "double buffer".

Without sweating too many implementation details, here's some pseudocode:

snapshottable<Vector3> data;
data.writable().x = 5; // write to the member x

// take read-only snapshot
const snapshottable<Vector3>::snapshot snap (data.createSnapshot());

// since no writes have happened yet, snap and data point to the same object

int a = snap.x; //would read from the old buffer's x member, e.g. 5

data.writable().x += 1; //this non-const access triggers a copy

// data & snap are now pointing to different objects in memory
// data.readable().x == 6, while snap.x == 5

In your case, you'd snapshot your state and pass it to render. Then you'd allow your update to operate on the original object. Reading it with const access through readable() would not trigger a copy... while accessing with writable() would trigger a copy.

I used some tricks on top of Qt's QSharedDataPointer to do this. They differentiate const and non-const access via (->), such that reads from a const object won't trigger the copy on write mechanics.

Hostile Fork
ooo, very interesting, I will definitely investigate this, thank you.
Gary
sounds like it could save some memory too
Gary
You can do copy-on-write many ways, but if you are interested in a thread-safe solution using QSharedDataPointer, my under-construction project is open source. Some of the devils-in-the-details are in the snapshottable.h: http://gitorious.org/thinker-qt/thinker-qt/blobs/master/include/thinkerqt/snapshottable.h
Hostile Fork
thank you, I'll take a look. I haven't used Qt, but it's definitely on my list :-).
Gary
+2  A: 

The larger your game state becomes, the more expensive it's going to be to keep two copies in sync. It'd be just as simple to create a copy of the game state for the render thread each tick; you're going to have to copy all the data from the front to the back buffer ayways, so you might as well just do that on the fly.

You could always try to minimize the amount of copying between buffers, but then you've got the overhead of keeping track of which fields have changed so you know what to copy. That's going to be a less than stellar solution in the core of a video game engine where performance is pretty key.

meagar
Don't have to keep them in sync explicitly, necessarily. For instance, I can have my physics engine read from all the old buffers... do its operations, and write into the new. The first thing that changes the objects in my game loop should do enough computation to keep them in sync.
Gary
+1  A: 

Maybe you would even like to create a new render state in each tick. This way your game logic is the producer and your renderer is the consumer of render states. The old state is read only, and can be used as reference for both rendering and the new state. Once rendered, you dispose it.

As for the small objects, the Flyweight pattern might be suitable.

Fozi
hmm, this is an interesting idea. Right now I've got my main data structure as a scene graph, and the nodes own the data. If I understand correctly, your suggestion would require separating data out into a different data structure and having the nodes link into it, but that could give some advantages as well. I haven't considered that.
Gary
+1  A: 

You need to do two things:

  1. separate object's own state and it's relation with other objects
  2. use COW for object's own state

Why?

For rendering purpose you only need to "back-version" object's properties that affect rendering (like position, orientation, etc.) but you do not need object relations. This will set you free of dangling pointers and allow to update the game state. COW (copy-on-write) should be 1-level deep, because you only need one "other" buffer.

In short: I think that choice of operator overloading is completely orthogonal to this problem. It's just sintatic sugar. Whether you write += or setNewState is completely irrelevant as both use up the same CPU time.

Milan Babuškov
+1  A: 

As a rule you should only use operator overloading when it is natural. If you're scratching around for a suitable operator for some functionality then it's a good sign that you shouldn't be forcing operator overloading on your problem.

Having said that, what you are trying to do is have a proxy object that dispatches read and write events to one of a pair of objects. Proxying object frequently overload the -> operator to give pointer-like semantics. (You can't overload ..)

While you could have two overloads of -> differentiated by const-ness, I would caution against this as it is problematic for read actions. The overload is selected by whether the object is referenced through a const or non-const reference and not whether the action is a actually read or a write. This fact makes the approach error prone.

What you can do is split access from the storage and create a multi-buffer class template and a buffer accessor template that accesses the appropriate member, using operator-> for syntactic ease.

This class stores multiple instances of the template parameter T and stores an offset so that various accessors can retrieve the front/active buffer or other buffers by relative offset. Using a template parameter of n == 1 means that there is only one T instance and multi-buffering is effectively disabled.

template< class T, std::size_t n >
struct MultiBuffer
{
    MultiBuffer() : _active_offset(0) {}

    void ChangeBuffers() { ++_active_offset; }
    T* GetInstance(std::size_t k) { return &_objects[ (_active_offset + k) % n ]; }

private:
    T _objects[n];
    std::size_t _active_offset;
};

This class abstracts the buffer selection. It references the MultiBuffer via reference so you must guarantee that its lifetime is short than the MultiBuffer that it uses. It has it's own offset which is added to the MultiBuffer offset so that different BufferAccess can reference different members of the array (e.g. template parameter n = 0 for front buffer access and 1 for back buffer access).

Note that the BufferAccess offset is a member and not a template parameter so that methods that operate on BufferAccess objects aren't tied to only working on one particular offset or having to be templates themselves. I've made the object count a template parameter as, from your description it's likely to be a configuration option and this gives the compiler the maximum opportunity for optimization.

template< class T, std::size_t n >
class BufferAccess
{
public:
    BufferAccess( MultiBuffer< T, n >& buf, std::size_t offset )
        : _buffer(buf), _offset(offset)
    {
    }

    T* operator->() const
    {
        return _buffer.GetInstance(_offset);
    }

private:
    MultiBuffer< T, n >& _buffer;
    const std::size_t _offset;
};

Putting it all together with a test class, note that by overloading -> we can easily call the members of the test class from the BufferAccess instance without the BufferAccess needing any knowledge of what members the test class has.

Also not the a single change switches between single and double buffering. Triple buffering is also trivial to achieve if you could find a need for it.

class TestClass
{
public:
    TestClass() : _n(0) {}

    int get() const { return _n; }
    void set(int n) { _n = n; }

private:
    int _n;
};

#include <iostream>
#include <ostream>

int main()
{
    const std::size_t buffers = 2;

    MultiBuffer<TestClass, buffers> mbuf;

    BufferAccess<TestClass, buffers> frontBuffer(mbuf, 0);
    BufferAccess<TestClass, buffers> backBuffer(mbuf, 1);

    std::cout << "set front to 5\n";
    frontBuffer->set(5);

    std::cout << "back  = " << backBuffer->get() << '\n';

    std::cout << "swap buffers\n";
    ++mbuf.offset;

    std::cout << "set front to 10\n";
    frontBuffer->set(10);

    std::cout << "back  = " << backBuffer->get() << '\n';
    std::cout << "front = " << frontBuffer->get() << '\n';

    return 0;
}
Charles Bailey