Alternative to inheritance

I have a renderer that renders a list of GraphicsModels(these contain the vertex, instance and index buffers stored on the GPU). Additionally, some of these GraphicsModels have their content updated on a separate worker thread. I therefore use a mutex as a part of this structure, so that it can be locked when the GraphicsModel is being updated or drawn.

However not all GraphicsModels are updated in a different thread so not all of them require a mutex. I guess the standard OOP way of solving this would be to create a GraphicsModel class and a descended MutexedGraphicsModel class with the second one containing the mutex.

What is the alternative for doing this if you would like to avoid OOP? Is it the fabled 'entity-based component system' that I am not supposed to invoke? :) Thanks.
Wait for someone more knowledgeable than me (I'm just a humble student, but I'm committing my thoughts to see if they make any sense).
I think you can just store an enum for each object (ToProcess, Locked, Processed) and do an interlocked exchange on that value (that is pretty free compared to a mutexand inheritance overhead, I suppose).
@pseudomarvin: If you use mutex, then how about if statement?
1
2
3
if (NeedToLock) Lock(&Mutex);
DoWork();
if (NeedToLock) Unlock(&Mutex);


@quien: using interlocked operations individually for each variable won't guarantee atomic operation on whole block that operates with these variables. Whole algorithm must be rewritten to work using interlocked operations. Sometimes it is a very complex change.

Edited by Mārtiņš Možeiko on
I was assuming that no more than a thread needs to work on the same object at a time, in that case a simple check if the exchange returned ToProcess would be enough I guess?
Ah, well I assumed opposite - that you'll need to access some shared data when working on object (because he mentioned mutex).

If all you do is isolated in each separate object, then yes interlock functions is all you need.
pseudomarvin
I guess the standard OOP way of solving this would be to create a GraphicsModel class and a descended MutexedGraphicsModel class with the second one containing the mutex.

This not "the standard OOP way". Actually, this is precisely the sort of "solution" that tends to create more problems than it solves. To be fair, it does preserve Liskov substitution, so the OO theorists will probably be okay with it.

I'm going to assume in what follows that you really need a mutex for some reason, but this may not be the case at all. A lightweight mutual exclusion mechanism (e.g. a spinlock) may be just as good. In fact, it's probably better in the case where you know locks won't be held for long, and at most two threads will want exclusive access to any object at any time.

If you really need a mutex (that is, if you really want a thread which is trying to acquire exclusive access to sleep if it can't get it), then one traditional way of doing this is called a "lock table". Conceptually, a lock table is a dictionary (e.g. hash table) which maps objects to locks. I think this originally came from databases, where you may want to lock individual rows of a database table.

One "pure OOP" way of doing it would be to make GraphicsModel purely abstract (i.e. what Java programmers would call an Interface), and then make two concrete classes (one thread-safe, one not) and share implementation by sharing a member. An even more OO solution is to use a decorator pattern, which adheres strictly to the single responsibility principle.

One "Modern C++" way of doing it would be to use policy-based design. You'd make your GraphicsModel class templated on the thread safety policy, and plug in which one you need. The other, if you're really up-to-date, is to use a generic monitor wrapper. What could be simpler, right?

The "software engineer in a hurry" solution would be to just use the mutex all the time. On modern platforms, mutexes are cheap enough for many purposes if there is no contention (and usually much cheaper than whatever they're protecting), and it protects the code from being misused in the future by someone who doesn't fully understand the code. That someone may well be you six months from now.

The "data oriented" solution is to look at the data and see what it's trying to tell you. Why are these GraphicsModels being rendered and mutated in different threads simultaneously in the first place? Did the data actually suggest that as a solution?
I think I have misled you a bit with the multithreading, what I am really looking for is a solution for the general problem where I have two structs which are exactly the same except that one of them has additional functionality. How do I augment this extra functionality without doing this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct A
{
   int foo;
}

struct AExtra
{
   int foo;
   int extraFunctionality.
}


@mmozeiko: If I understand correctly you are suggesting that I just leave the mutex in the struct and keep a flag telling me whether I should uset it? That would work of course but I was trying to avoid having to do this :).

@Pseudonym73: I should have said "the naive OOP way" since there are clearly more thought out ideas than mine was. The reason why these GraphicsModels are being rendered and mutated in different threads is that after the mutation computation takes quite a long time - it is therefore done in a separate thread. But when it is finished, I also need to upload the mutated mesh data to the GraphicsModel(the GPU). I decided to do this in the same worker thread but during this time, that GraphicsModel cannot be rendered hence the mutex. Maybe it would have been better just set a "hasToBeUpdated" flag in the worker thread for the GraphicsModel and let the main thread handle the updating of the data to the GPU.

Also I did not realize that a mutex is so slow compared with a spinlock, it may not really be necessary in my case.

Thanks everyone.

Edited by Dusan Drevicky on
The general solution is inheritence or composition, whichever you prefer.

But that's not really the point.

The underlying realization in data-oriented design is you don't need to solve the general problem. You have some specific cases of a problem, solve *those*. (If you don't have specfic cases, you don't have a problem to solve in the first place.) The circumstances under which you are solving the problem will give you clues as to how you need to solve it.

Some questions to ask yourself:

When does the data get accessed?
What things get accessed together?
Does all of this data need to be stored together?
- If they're accessed separately, the answer is usually "no".
Can I store this data implicitly?
- Ex: instead of a 'live' flag on pooled objects, keep the live ones packed together and keep a count -- the rest are implicitly dead.

Consider changing how you look at things. An example, from the last game I worked on:

I had a list of entities, each knew what kind of renderable (sprite, text, solid polygon) it was and all the data needed to do so. (Using a tagged union.) I would loop over the entire list, switch on the type, and produce render commands for each. This was ugly and bug prone.

The replacement system inverted things. I had lists of each kind of renderable, which had a handle to their host entity. Instead of rendering entities, I rendered renderables, and the entity itself no longer contained the data only needed to render. (The bits the entity code *did* care about, like the position/rotation, were stored together and referenced via the handle.) Because the size of the rendering data was different for different types, spliting it out in this way made entities smaller, as well.

This is a (small) example of the entity-component model you mentioned.

That's not the general solution, but it's the best solution for that particular problem in that particular codebase. Avoid grouping data together just because you feel they "belong" together -- group data by what is actually used together.

This doesn't solve your problem, really, but I hope it gives you a push in the right direction.
pseudomarvin
I should have said "the naive OOP way" since there are clearly more thought out ideas than mine was.

Just to be clear, I don't think they're all good solutions. Quite the contrary, in fact.

pseudomarvin
The reason why these GraphicsModels are being rendered and mutated in different threads is that after the mutation computation takes quite a long time - it is therefore done in a separate thread.

Right, so this GraphicsModel has a life cycle.

I'm going to assume that this is some kind of game. Does this mutation happen every frame? What are the consequences if some models are "done" by the time they're ready to render and some are not?

It's issues like this that motivate the "three frames at a time" model that many games adopt. At any time, three frames are "happening": one frame is being displayed, the frame after it is being drawn, and the frame after that is being "prepared" (the "mutation" in your example). It requires more buffering, but it's much easier to reason about, and also has the advantage that the thread(s) doing the drawing don't have to contend for any resources.
@btaylor2401: Okay, I think I understand the data-oriented philosophy a bit better now. I guess I should take a look at the data and see what that tells me instead of trying to fit it into a predefined composition.

@Pseudonym73:
It's issues like this that motivate the "three frames at a time" model that many games adopt. At any time, three frames are "happening": one frame is being displayed, the frame after it is being drawn, and the frame after that is being "prepared" (the "mutation" in your example). It requires more buffering, but it's much easier to reason about, and also has the advantage that the thread(s) doing the drawing don't have to contend for any resources.
That is a great idea, I haven't thought of that. The three frames at a time strategy might not be enough since the compututation might take more time than that (I am actually trying to procedurally generate tree meshes), but I could use something like ping-ponging between two GraphicsModels, only drawing the updated one when the data has been uploaded so no mutexes are really necessary.

Thank you both for your help. :)