Jump to content

Syncing objects between threads


Josh
 Share

Recommended Posts

I was wondering if there is any sane / simpler way to sync object data between threads other than creating a complicated messaging system like below? This is going to take a very long time to write all the possible values that might be modified:

const int THREADMESSAGE_CREATECAMERA = 1;
const int THREADMESSAGE_UPDATEENTITYORIENTATION = 2;

shared_ptr<Camera> CreateCamera(world)
{
	shared+ptr<Camera> camera = make_shared<Camera>()
	camera->world = world;

	//Add instruction to command queue
	Instruction instruction;
	instruction.id = THREADMESSAGE_CREATECAMERA;
	instruction.argument[0] = camera;
	instructions.push_back(instruction);
  
	return camera;
}
  
void Entity::SetPosition(const Vec3& position)
{
	this->position = position;
	UpdateMatrix();
  
	//Add instruction to command queue
	Instruction instruction;
	instruction.id = THREADMESSAGE_UPDATEENTITYMATRIX;
	instruction.argument[0] = this;
  	instruction.argument[1] = this->mat;
	instructions.push_back(instruction);
}

It seems like there should be a better way to do this in a more automated fashion, perhaps with templates and function / member pointers:

//Add instruction to command queue
AddInstruction( this, &Entity::color, this->color );

Some messages aren't just setting a member, some are a more complex function. This function, for example, would require additional code to send the position data to the VBOs on the graphics card:

//Add instruction to command queue
AddInstruction( this, &Surface::SetVertexData, this->positiondata->Copy() );

So what we want is some kind of Instruction object that can accept an object, a method or member pointer, and a list of arguments of any type. There's got to be something like that out there already(?)

Here is code to get the pointer to a member and a method, and to use them:

// declare pointer to member
Vec3 Entity::*member = &Entity::position;

// declare a pointer to method
void (Entity::* method) (const float, const float, const float, const bool) = &Entity::SetPosition;

auto entity = Pivot::Create();
entity->*member = Vec3(0);
(entity->*method) (1,2,3,false);

And then a templated class, something like this?:

template <class T, class Y, class P>
class Instruction
{
	shared_ptr<T> o;
  	<T>::*(something) function;  
	arguments<Y>[...] args;

	Instruction(shared_ptr<T> o, arg0<Y>, arg<P>...)
	{
		this->o = o;
		for each argument {
			arguments[n] = arg[n];
		}
	}

	void Execute()
	{
		o->*function(arg[0],arg[1],arg[2]...);
	}
}

 

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Looks like variadic templates is what you're looking for?

https://stackoverflow.com/questions/1579719/variable-number-of-parameters-in-function-in-c

 

You could use macros.

You could create Instruction1<T>, Instruction2<T,A>, Instruction3<T,A,S> etc etc

 

With modern compilers using std:function might be the way to store function pointers vs the old way.

 

Link to comment
Share on other sites

I got it working. :) The syntax is nice and simple:
 

	class Foo
	{
		int num = 1;
	public:

		void Print()
		{
			printf("%d\n",num);
		}

		void Add(int n)
		{
			num += n;
		}
	};

	std::vector<std::function<void()> > commandbuffer;

	auto f = make_shared<Foo>();

	commandbuffer.push_back(std::bind(&Foo::Add, f, 1));
	commandbuffer.push_back(std::bind(&Foo::Print, f));
	commandbuffer.push_back(std::bind(&Foo::Add, f, 36));
	commandbuffer.push_back(std::bind(&Foo::Print, f));
	commandbuffer.push_back(std::bind(&Foo::Print, f));

	for (int n = 0; n < commandbuffer.size(); ++n)
	{
		commandbuffer[n]();
	}

Output:

2
38
38

 

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

I don't think there is any way to bind a member to be set to a value, without a setter function, but that's okay. This does what I need.

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

On 4/8/2018 at 3:02 AM, Josh said:

I don't think there is any way to bind a member to be set to a value, without a setter function, but that's okay. This does what I need.

You should just use a lambda expression?

Color4 color(255, 0, 0, 255); // I forgot what structure LE's colors are.
AddInstruction( [this, color]() { this->color = color; } );

AddInstruction( [this]() { this->positiondata->Copy(); );

// Or whatever youre doing for the function....

It might pay off to just use lambdas and std::function.

This is the most common practice for thread "job pools".

It is likely to be more or less the exact same, they're both not pretty to read but using a lambda and it's capture list along with std::vector<std::function<void()>> to hold these functions gives you a lot more freedom in the long run.

It is not going to limit you any more as in the lambda you can edit public member variables without a setter function, for private members you would still need a setter but that's obvious. For bind you need a setter regardless of if the member is public/private.

I generally do a design similar to this: https://github.com/SaschaWillems/Vulkan/blob/master/base/threadpool.hpp

Sascha released it under the MIT license, you should use it. It is more or less exactly what you need.

Link to comment
Share on other sites

This is great. Awesome stuff. I did not know you can do this with C++, it makes my life a lot easier:
 

	class Foo
	{
	public:
		int num=1;

		void Print()
		{
			printf("%d\n",num);
		}

		void Add(int n)
		{
			num += n;
		}
	};

	std::vector<std::function<void()> > commandbuffer;

	auto f = make_shared<Foo>();
	 
	commandbuffer.push_back(std::bind([f]() { f->num = 100; }));
	commandbuffer.push_back(std::bind(&Foo::Add, f, 1));
	commandbuffer.push_back(std::bind(&Foo::Print, f));
	commandbuffer.push_back(std::bind(&Foo::Add, f, 36));
	commandbuffer.push_back(std::bind(&Foo::Print, f));
	commandbuffer.push_back(std::bind(&Foo::Print, f));

	for (int n = 0; n < commandbuffer.size(); ++n)
	{
		commandbuffer[n]();
	}

 

  • Thanks 1

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Nice, I guess it makes sense they both return a function, I did not ever think that they would work together. Good to know.

If std::bind is returning a std::function<void()>, it is likely you don't need to wrap the lambda in a call to std::bind() either. Keep in mind std::bind is for class members, not lambdas.

  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...