Click here to Skip to main content
15,887,596 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Consider the following code snippet( Python):
class Task:
	def __init__(self,prev):
		self.threads = []
		assert isinstance(prev,Task)or isinstance(prev,Game)or prev is None
		if isinstance(prev,Task):
			self.prev = prev
			self.game = prev.game
		else: self.prev,self.game = None,prev
		self.frame = 0
	def idle_func(self): return self.idle_func
	def __call__(self):
		assert self.game is not None
		for thread in self.threads[:]: thread()
		self.frame+=1
		return self if len(self.threads)> 0 else None


And then this also( Python):
class SDL_TEST(Task):
    def __init__(self,game):
        FrameTask.__init__(self,game)
        self.threads = [self.sample_paint,Text(self,["This message was displayed","in two frames."])]
    def sample_paint(self):
        self.game.surf.fill((255,0,0))


This works well, and all. However, consider the same in C++:
C++
class Task
{
	protected:
		typedef void (Task::*Mode)();
		std::vector<Mode> threads;
	public:
		bool operator () ()
		{
			std::vector<Mode> cp_threads(threads);//Make a copy; prevent freakish behavior in the event of a thread terminating.
			for( int i=0; i<cp_threads.size(); i++)
				(this->*cp_threads[i])();
			return threads.size() > 0;
		}
};


I can't remember exactly what it was I typed up, but it was similar to this. I'm working on a game engine in my spare time at school, but my teacher demands that it be done in C++( my preference is Python, personally, but meh). Anyways, I've been trying to work this out, but I was unable to get it to work. Here's the idea:
A Task has multiple threads. If a thread terminates, it removes itself from the threads-list, which it has access to because a 'thread' is really just a member-function of a Task( which is how things should be, I believe. ) Would anybody be wiling to clue me in on what they think I should do?

--Clarification:
No, these threads are not literal threads. -- Merely functions that are called once per loop.
Also, in the second Python snippet, Text is another class that inherits from Task. Since __call__ is overloaded, and still takes 0 arguments, this is perfectly fine. Perhaps I should do something like:
C++
class Callable
{
public:
	virtual void operator () () =0;
};
class Task: public Callable
{
	std::vector< Callable> threads;
};

The only problem I would have with this is that I would need to have threads have a reference or pointer to their respective tasks in order to have access to things, which I'm not too crazy about.
Perhaps even something like this:
C++
class Callable{/*...*/};//see previous snippet.
typedef void (Callable::*Thread) ();
class Task: public Callable
{
	private:
		std::vector< Thread> threads;
	public:
		/* ... */
		operator Thread () const{ return &Task::operator(); }
}


Basically, I'm asking what class model I should take to do the following:
Tasks have threads. Threads are member functions of a Task. A task can also be appended to a Tasks' threads list.

Further:
I have my own SmartArray class which is essentially a ( performance-wise) better version of std::vector<>. To use it, a class needs to inherit from the class SmartObject ( for a reference-count -- I also implemented a Reference class that is nested within SmartArray, which is template-based. If you're interested, link here[^], however it's a lengthy file and ultimately not the point of this post so I haven't pasted it into this post. ) How would I go about making a type-def'd type inherit the SmartObject type? Or is something like that even possible? Would it be better to do the following?
C++
class CallerHolder: public SmartObject // Needed for use with SmartArray<> and SmartArray<>::Reference uses.
{
	private:
		const Thread function;
	public:
		CallerHolder( Thread func): function( func){}
		CallerHolder( const CallerHolder& cp_ch): function( cp_ch.function){}
		void operator()( Callable* caller){ (caller->*function)(); }
};
Posted
Updated 16-Feb-12 16:39pm
v3
Comments
TRK3 16-Feb-12 20:10pm    
Please improve the question:

(1) Your two python code snippets appear to be exactly the same, so...???

(2) Are the "threads" you are talking about here, actually system threads, or (as your code seems to imply) are they actually just functions that get called once per loop?

(2) What are you actually asking? Your C++ code snippet doesn't actually demonstrate a problem or issue. How is "Mode" defined?
Tyler Elric 16-Feb-12 22:40pm    
I believe I've improved it as you've described, sorry for the mis-paste! :(
Thank you for your reply, however. :)
TRK3 17-Feb-12 14:18pm    
Thanks for the clarification.

1) What's wrong with threads having a pointer to their respective parent tasks? It's a pretty common thing to do. Are you worried about circular references with your reference counted pointers?

2) Why do you need to use reference counted pointers for the task and threads? Seems pretty unlikely that these objects are being passed around willy-nilly. Don't you have complete control over their life cycle and where they are being referenced?

Seems the problem is that you are causing yourself all kinds of headeaches because you are coming from a garbage collected environment and you've never developed the necessary habits/discipline to program in a non-GC environment.

This problem really doesn't need GC or reference counted smart pointers.
JackDingler 17-Feb-12 14:53pm    
TRK3 is right. It's normal to maintain a (void *) to pass the function. This way the function has some context for it's duties. In C++, the (void *) is often a pointer to the class object.

Also, in this case, the methods are often static members of the class. It is common in these static members to derefence the pointer, so that it can call methods in the class.
Tyler Elric 17-Feb-12 15:57pm    
Heh. You should see some of my old code...
The reason I'm using my own GC system is because I very much like Python's use of slice's, and such. For example, a Filename without it's extension or path is this:
filename[filename.rfind('/'):filename.rfind('.')]
And with my SmartArray, I can do this:
SmartArray<type> copy_of_items( src_items[Slice(-3,-1)])
Which would work just like:
copy_of_items = src_items[-3:-1]
would work in python.
The other advantage is that the SmartArray implements buffering in it's allocation of memory, so it's much better ( performance wise) than std:: as far as I can tell. However, I didn't want to have a billion objects whos values should be synchronized, as would happen with std::vector. So I created the Reference class. This way a single object could be referenced many times, and there would still only be one allocation. Of course, you can't do this with stack-allocated variables, so you should allocate any smartobjects that will be referenced ( by a smartarray, or other reference's) off the heap using new. So I guess my issue is sharing of data, and that's why I'm using my GC rather than duplicating a ton of different objects.
Consider this:
A Pokedex. What is it? It's a tool for recording and displaying information on species of Pokemon. In Pokemon games, a player may only see species' info that he has caught. So the Pokedex ( contains information on which species he has caught -- recording) and the Data( the information that has been 'recorded' ) should be two separate entities. However, all Pokedex should share the same data, but only differ by which species have been seen/caught/etc. Continuing with this concept, a save loader would do as it's name implies, then launch an overworld task with the save that was just loaded. Without GC, I would either use a reference( this is where circular pointers would come into play -- not a problem necessarily, I just don't like it ) or a pointer to the loaded save( or create another save via copy constructor), and pray that the Intro task doesn't go out of scope somehow while I'm in the Overworld task. Then the Overworld will need to pass data to a Battle Task. That's already about 3 instances of the 'same' (equivalent -- not same as in location in memory) data object. ( well, as the game engine's loop is set up, it doesn't make sense to not allocate things dynamically. )
<pre lang="python">
# The engine in question is at school, and being the weekend I don't have access to it. However, the function is very similar to this:
class Game:
def __init__(self,res=(640,480)):
self.task = None
self.surf = pygame.display.set_mode(res)
pygame.display.set_caption(type(self).__name__)
self.pictures = Data("images",".png",load_img)
self.sprites = Data("sprites",".png",load_trans_img)
self.music = Data("music",".mid")
self.maps = Data("maps",".json",load_json)
self.fonts = Data("fonts",".png",load_font)
self.tilesets = Data("tilesets",".json",load_trans_img)
self.pressed = None
def registerTask(self,task):
assert isinstance(task,Task)
task.game = self
self.task = task
def __call__(self):
c = pygame.time.Clock()
while True:
events = pygame.event.get()
self.pressed = None
for event in events:
if event.type == pygame.QUIT: return
elif event.type == pygame.KEYDOWN:
self.pressed = pygame.key.get_pressed()
task = self.task
if task() is None: break
pygame.display.flip()
c.tick(30)
</pre>

In short, allocating tasks off the heap, would be problematic with this program flow.

However, the user will not always be in one specific task, so it makes sense to allocate, and deallocate those as needed. Threads on the other hand, are member functions of a class. So you can't really allocate or deallocate them, but sometimes you want to call them and other times you don't. However, for the sake of consistency among tasks, re-defining Task::operator () in derived Task-classes is frowned upon, I think( am I wrong in say

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900