1

I know, I know. The question just screams "you shouldn't be in that situation!" but hear me out...

I have a game engine that performs the updates in one thread and the renders in another. The renders want to read the data from the previous frame while the update updates the data as it would be for the next frame.

So, to solve this I have a POD struct, mutable_data, that is everything in the object that can change (velocity, position, etc...) which I have two instances of inside the object, one const and the other not. At the end of one iteration of the game loop the non-const data will be the up-to-date data object and I want to then copy this over the const instance so that they are now equal and ready for the next iteration.

Is this possible?

I've drafted up an example program that hopefully makes it clear what I mean:

#include <iostream>

struct mutable_data
{
  int i = 1;
  bool j = true;
  char k = 'a';
};

struct my_data
{
  my_data() : constData() {} 

  int i() const { return constData.i; }
  bool j() const { return constData.j; }
  char k() const { return constData.k; }

  void set_i(int i) { data.i = i; }
  void set_j(bool j) { data.j = j; }
  void set_k(char k) { data.k = k; }

  void update() { constData = data; }

private:
  mutable_data const constData;
  mutable_data data;
};

int main()
{
  my_data d;
  std::cout << "i: " << d.i() << ", j: " << d.j() << ", k: " << d.k() << std::endl;

  d.set_i(10);
  d.set_j(false);
  d.set_k('z');

  std::cout << "i: " << d.i() << ", j: " << d.j() << ", k: " << d.k() << std::endl;
  std::cout << "===============" << std::endl;

  d.update();

  std::cout << "i: " << d.i() << ", j: " << d.j() << ", k: " << d.k() << std::endl;
}
Sam Kellett
  • 1,277
  • 12
  • 33
  • 4
    why is the const one marked const, if it's not meant to remain constant? – Adam Jan 30 '14 at 20:05
  • Comment on your data structure: wouldn't it work better to keep two separate data trees with top level pointers that swap which is for update and which is for read? Rather than keep two sets of data inside each object. – Zan Lynx Jan 30 '14 at 20:05
  • @Adam because it is a const object. I don't want anything to ever play with it... except when it needs to be updated. I know I can just declare it not const, but it feels way more appropriate for it to be const in this situation. No? – Sam Kellett Jan 30 '14 at 20:08
  • And then updates might look like `update->obj[index].position = read->obj[index].position + read->obj[index].delta` or similar. – Zan Lynx Jan 30 '14 at 20:08
  • @ZanLynx swapping them wouldn't be correct because the now-out-of-date "read" data becomes the soon-to-be-updated data and yet it is missing the most recent iteration of modifications. Does that make sense? – Sam Kellett Jan 30 '14 at 20:09
  • @Sam: Where I saw it done, everything in update was written as updated copies of read. So it was never out of date. There may have been some bugs with using old values from update. – Zan Lynx Jan 30 '14 at 20:11
  • @Sam No, it's not appropriate to declare it const if you intend to change it, even if through a controlled path. The way to go is to declare an accessor that returns a const reference to your mutable data. That way all the rest of the code sees is a const struct, while you're free to add a function that can update this structure. – Adam Jan 30 '14 at 20:12
  • Also, if you're serious about getting good threading performance, mixing your read-only and update objects close together like this will cause excessive cache-line sharing. – Zan Lynx Jan 30 '14 at 20:14
  • More thoughts on the const-ness... We're allowed to have const methods that are not const by declaring mutable variables... What I'm looking for is the opposite. A mutable method that can alter const data? I don't want anything else to be able to modify it. – Sam Kellett Jan 30 '14 at 20:14
  • Another way to avoid cache line sharing is to pad your data out to the size of a cache line. More info about it here: http://software.intel.com/en-us/articles/avoiding-and-identifying-false-sharing-among-threads – Zan Lynx Jan 30 '14 at 20:28
  • 2
    @Sam: "because it is a const object. I don't want anything to ever play with it... except when it needs to be updated" -- you never want *any* object to be modified except when it's supposed to be modified. That doesn't mean every object should be `const` ;-) `constData` is a private member of its class and the only code in the class that modifies it is in `update()`. That's enough to enforce the constraint that only `update()` modifies it. Provided your classes are small and focussed, there is no practical danger of you accidentally writing code to modify it elsewhere. – Steve Jessop Jan 30 '14 at 20:40

2 Answers2

4

Use accessors for your two data structures, one for the current (const) one and another for the upcoming one.

mutable_data & currentData() { return *currentPtr; }
const mutable_data & upcomingData() { return *upcomingPtr; }

void update() { std::swap(currentPtr, upcomingPtr); }
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Swapping wouldn't work I don't think... doesn't it have to be a copy? Say currentData is on frame 10, upcomingData is now on frame 11. So, when you swap, upcoming goes back a frame to 10 and yet we're on frame 11 and frame 12's data is about to be written even though frame 11's data is not there. Does that make sense? Sorry I find it hard to explain this! :) – Sam Kellett Jan 30 '14 at 20:12
  • @Sam you can do a copy too if you want, you didn't show enough logic to know if it was necessary. The point is that neither pointer is `const` so you can do anything at that point - the only restrictions are on the consumers of `currentData` and `upcomingData`. – Mark Ransom Jan 30 '14 at 20:15
  • I see, yeah I think you're right -- that would be the best way to go. Thanks! – Sam Kellett Jan 30 '14 at 20:17
1

This is a very interesting question. I think what you getting at is the issue with data race when implemented under conditions suitable for concurrency.

As discussed in a separate question, one possible way to manipulate what you want to be const is to modify the variable as mutable.

Of course, in order to accomplish this end, the struct would have to be redesigned. Having a const method to initialize the said variable will enable you to lock-in the value you want.

In addition to the keyword mutable, you should think about using const_cast to recast your variable.

Community
  • 1
  • 1