will we ever get multi threading in BlitzMax?

BlitzMax Forums/BlitzMax Programming/will we ever get multi threading in BlitzMax?

*(Posted 2008) [#1]
TBH its a requirement for loads of stuff if you want a good system for networking and such like, its also helpful for developing business like applications.


Jim Teeuwen(Posted 2008) [#2]
I think it's already been stated many times that there is No intention to include multi threading, at all.

There should be a post by Mark around somewhere which elaborates on this.


Zethrax(Posted 2008) [#3]
Here's a post where Mark Sibly mentions his possible plans regarding threading.

http://www.blitzbasic.com/Community/posts.php?topic=75871#848486


Hi,

Some nice ideas there, but I'm now 87% certain I'll be tinkering with the GC system soon in an attempt to at least make brl.blitz thread safe.

In fact, the reflection stuff in there at the moment means it's almost possible to write a custom GC, except that the system doesn't yet have a list of root (ie: 'Global') objects.

This will likely be an ongoing mission. There are many other things that will need to be taken into account (esp: exceptions) but I think by taking things a step at a time we'll get there eventually.




ImaginaryHuman(Posted 2008) [#4]
I don't think I'd hold my breath to see threads working within the next 12 months. Mark is focussing on Max3D right now and hopefully till its completion.

You could try some kind of mishmash of using processes but it's harder to share data between them.


markcw(Posted 2008) [#5]
As an example, Google Chrome doesn't use threading, it uses multiple processes. So do we really need the added complexity of threading for a language primarily intended for games?


plash(Posted 2008) [#6]
markcw: But are those OS processes, or are they simulated processes?


Jim Teeuwen(Posted 2008) [#7]
They are OS processes.


Mark Tiffany(Posted 2008) [#8]
Much like was discussed as a possible approach in the last thread mark had on this topic. The comic that describes Google Chrome was actually rather interesting in this area...


Jim Teeuwen(Posted 2008) [#9]
Here's a screenshot of my windows taskmanager showing 1 Chrome window with a couple of tabs open.



You can use this approach quite easily and still allow for adequate communication between the individual processes. Passing of serialized data structures can be done using a local TCP channel.

Your main app basically serves as a server which listens at a specified local tcp port for child processes.. When a child process spawns, it connects to said port, announces itself to the main app and uses this same connection to transfer required data back/forth.

From a 'best-practise' standpoint it is recommended to keep this communication to a minimum, but in theory you can make it as complex as you want. I use a similar approach in one of my C# projects. It allows me to isolate/sandbox Plugins in seperate procesesses. So if the pluging makes a booboo, The application itself, as well as the other plugins are not affected by the crash and continue working properly. It's safer, faster and less memory intensive.. As well as easier to profile the performace of individual code segments running in their own process.

I suppose the only downside is the massive list of identical process names in your windows taskmanager.


ImaginaryHuman(Posted 2008) [#10]
This is an interesting approach and I think I will experiment in this area in future. I don't think we're going to as easily see threads as much as we can use processes, and the process module is already functioning cross-platform. (isn't it?)

It's a different design philosophy between the two approaches. It's a matter of either saying `the separate parts of my app are inside my application's memory space` or `the separate parts of my app are outside my application's memory space`.

Threads give you usually lower overhead to switch between them, and they are local inside the application, so they have full access to all variables, all loaded data, any GUI, all game objects, graphics rendering, etc. It's all integrated into one environment. Each thread isn't kept `separated off` from the main application or the data it's using. You can just assume you have access to everything. However, with that much openness you now have the problem of how not to conflict accesses to things at the same time, so you have to implement some kind of locking and stuff like that to separate out the threads from each other. Since you start out with total openness you have to deliberately shut it down somewhat to make it function well.

Processes start out being more isolated, they each have to have their own memory space like an entirely separate program, it doesn't know anything about the other apps and can't even access any of their data without adding functionality to do so. It starts totally isolated but can become more open - but not really to the extent that it could share all of your variable, or write to the same graphics screen, or play the same sounds using those you loaded in the other app, or as tightly integrate into things like a physics system accessing all objects in the other app's memory, or doing mass particle systems, etc. These things become less efficient and require more `communication` to transfer data back and forth. But it can be done.

With processes you have to start thinking about your application as a collection of networked machines, like you guys are saying above. Unless you set up shared memory (it's been done, I think GreyAlien made a post about it for Windows at least), you've gotta assign certain key functions to a main server process - it has to be able to create a screen and do almost all of the work to render graphics and probably to play sounds too. I presume you would get some kind of lag by using a network for inter-process communication, or even via the o/s provided communication channels, that you wouldn't want such a lag in a game causing sounds in one app to play a few frames behind when the event was shown in another app. So one app's gotta do some of the heavy lifting, and then you've gotta find other parts of the system that can be shovelled off to some other process to work on. You can't really transmit a lot of data over the network all the same so dealing with physics for large numbers of objects is a bit tricky. So the question remains what would those helper app processes actually spend time doing?

What would be nice is to get something working using something like GreyAlien did regarding sharing memory - get the processes to share memory safely on all 3 platforms, allocate some large memory buffer containing all game objects and data, THEN the other processes could really go to town working on things like physics and logic and audio much more responsively than transmitting packets over a network. How feasible is that? Does Mac and Linux have memory sharing between processes?


Ian Thompson(Posted 2008) [#11]
TCP IP or any local network protocol is not fast enough, you need shared memory with MUTEXs to control access between processes.

The whole 'process' of processes is really just running seperate programs in different threads, which to me at the very least is klunky and I doubt it would work very well for real-time critical applications.


ImaginaryHuman(Posted 2008) [#12]
Using processes sounds more possible and viable right now than having no threads at all. But I do agree using the network should only be used to access other computers unless you want some lag delay between processes. Shared memory seems like an nice option but looks like we'd need code written on each platform in C or something.


Ian Thompson(Posted 2008) [#13]
Setting up shared memory is not difficult its a simple API call(I did one years ago to glue a GUI built in Delphi to use and control a Blitz3D window as its rendering context, the shared memory sat in a DLL that they both had access to), its kinda treated like a file in memory but you have to be careful with access to the shared memory as there is no automatic way for one process to know if another process is accessing the same data. So locking(via mutex), has to be done in a similar fashion to threads. Plus side is its a lot cleaner and easier to use non thread safe code. Ironically as each non thread safe process is launched, it gets its own thread, so this allows thread unsafe code to run multi threaded. Downside, interprocess communication, using shared memory for example, is the only way for processes to communicate with each other. Another plus as each process is a separate entity, a process could crash and the shared memory other processes would remain intact, this allows for pretty robust programs with any stressed code being resigned a 'disposable' process that can be monitored and relaunched if necessary.


I only know windows but Id imagine a simple cross platform API could be designed to allow this functionality between the various O.S's.


Jim Teeuwen(Posted 2008) [#14]
Shared memory is fast, but I am not at all a big fan of the potential access violations. I prefer to isolate everything as much as possible and for the few bits of data that need to be transfered between processes, the network approach has worked fine. Mind you, my application is not nearly as performance critical as a game.

The reason I am so anal about the sandboxing, is because the child processes contain user-written plugins for my application. Since I cannot guarantee what a developer creates in his plugin, I have to assume the worst and treat it as potentially dangerous malware. By isolating it this way, I can ensure that it never accesses memory it should not see as well as being able to monitor it for dubious code execution at which point I can simply terminate it without negatively affecting any other plugins or the main app.

One way to use shared memory and still be able to make sure it is only every modified by a single process, you can go for a Contract based implementation used in Singularity (A Prototype Operating system from Microsoft Research) which does much the same, but on an OS level.


Ian Thompson(Posted 2008) [#15]
Careful programming removes all potential access violations, like I said, I used mutex's to exclude access when the shared memory was being written to or read from, it worked very well.


Winni(Posted 2008) [#16]

TBH its a requirement for loads of stuff if you want a good system for networking and such like



No, you don't. Just as an example, the entire Python networking core is single process/single thread, and it is obviously capable of powering something as huge as YouTube.

You only need multi-threading in networking applications when you use blocking I/O (like Java before 1.4 always did; somebody at SUN thought this would help them sell more server hardware).



Its also helpful for developing business like applications.



That's a different story. Multi-threading helps making applications appear more responsive, and it is very useful to let time-consuming tasks run in the background. But that could also be achieved with a separate process.


Careful programming removes all potential access violations, like I said, I used mutex's to exclude access when the shared memory was being written to or read from, it worked very well.



You basically simulated multi-threading with multiple process - I guess that's similar to how threads were (or still are) implemented in Linux: As processes that share the same memory space.

This certainly works, but it wipes out one of the major advantages of using processes (instead of threads): Separate memory spaces, which increase robustness and stability. When you share memory, you can blast the entire system into Never-Neverland, and debugging such an application is a nightmare of its own - and that is something that it shares with multi-threaded programs: Easy to crash, hard to debug.

I no longer believe that BlitzMax will ever get multi-threading support, at least not in version 1.x. Probably its entire core has to be re-written to get it done, and as has been pointed out multiple times, BRL is focusing on the new 3D engine right now and BlitzMax is not positioned as an application development language (so much for 'helpful for developing business like applications').

If you want a multi-platform language that gives you multi-threading and that is great for writing business apps... Make sure you like coffee. ;-)


plash(Posted 2008) [#17]
They are OS processes.
Interesting.

You can use this approach quite easily and still allow for adequate communication between the individual processes. Passing of serialized data structures can be done using a local TCP channel.
I wonder what Chrome uses, though.

TBH its a requirement for loads of stuff if you want a good system for networking and such like
Aye, blocking I/O calls are a pain in the arse.


JoshK(Posted 2008) [#18]
I was able to make a child process return data in bytes, not text. I don't know how you would pass data from the main app to the process.

main.bmx:
Type TMultiProcess

	Field process:TProcess
	
	Method ReceiveMessage()
		
	EndMethod
	
	Function Create:TMultiProcess(path$)
		Local multiprocess:TMultiProcess
		multiprocess=New TMultiProcess
		multiprocess.process=CreateProcess(path)
		If Not multiprocess.process Return
		Return multiprocess
	EndFunction		
	
	Method Free()
		process.Close()
		process=Null
	EndMethod
	
EndType

Local multiprocess:TMultiProcess
multiprocess=TMultiProcess.Create("process.exe")

Delay 5000

For n=1 To 100
	If multiprocess.process.pipe.ReadAvail()
		Print String(multiprocess.process.pipe.ReadByte())
	EndIf
Next

End


process.bmx:
StandardIOStream=New TCStandardIO

Local n

Repeat
	StandardIOStream.WriteByte(n)
	n:+1
Forever



JoshK(Posted 2008) [#19]
Ha, you can send data to the process like this.

Seems like you can do one or the other, but not both.

main.bmx:
Type TMultiProcess

	Field process:TProcess
	
	Method ReceiveMessage()
		
	EndMethod
	
	Function Create:TMultiProcess(path$)
		Local multiprocess:TMultiProcess
		multiprocess=New TMultiProcess
		multiprocess.process=CreateProcess(path)
		If Not multiprocess.process Return
		Return multiprocess
	EndFunction		
	
	Method Free()
		process.Close()
		process=Null
	EndMethod
	
EndType

Local multiprocess:TMultiProcess
multiprocess=TMultiProcess.Create("process.exe")

Delay 5000

For n=1 To 100
	'If multiprocess.process.pipe.ReadAvail()
		'Print String(multiprocess.process.pipe.ReadByte())
		WriteByte multiprocess.process.pipe,100+n
	'EndIf
Next


Delay 50000
End


process.bmx:
StandardIOStream=New TCStandardIO

Local n

Repeat
	'StandardIOStream.WriteByte(n)
	
	n:+1
	'If StandardIOStream.ReadAvail()
		Notify "Process: "+StandardIOStream.ReadByte()
	'EndIf
	
Forever



Yahfree(Posted 2008) [#20]
Thats pretty cool, its like multithreading, you just have to hope they don't freak out seeing 30x "process.exe" in their task manager.


ImaginaryHuman(Posted 2008) [#21]
One other MAJOR advantage of threads or multiple processes is simply to take advantage of multiple CPU's or cores within CPU's, which is now very common and will be even moreso in future - that can help boost performance in almost any type of application, networking or otherwise. Got a dual-core CPU? Want double the performance? You got it. And imagine if you had a quad-core or 8 or 16 core CPU? Are you going to settle for 1/8th or 1/16 the possible CPU power for your app?

I suppose you could potentially launch multiple processes as part of a multi-process app (like google Chrome) and give each process a different name so the user doesn't think something is open too many times - like name it VirtualCPU1-MyProgram.exe, VirtualCPU2-MyProgram.exe, or even like Program-ApplicationHandler.exe, Program-GUIManager.exe, Program-Physics.exe etc

One thing you can also do (just an idea) is have each of the open processes monitor the other open processes for the multi-process app, and when one of them detects that one of the processes has shut down and it really should be open, you re-launch it, bringing it back online - and you also give the option within any single process/program/interface to be able to shut down the whole group.

It looks like shared memory is the way to go for processes on the same computer, and then some kind of remote procedure call thing for over a network.


JoshK(Posted 2008) [#22]
This would be ideal for a system that performs some intensive processing on a set of data, and returns a result. If we were still using lightmaps, that would be a perfect application.

I'm not sure if this will work for real-time apps. I'd like to figure out how to send and receive data to and from a process and try it out.


Jim Teeuwen(Posted 2008) [#23]
As far as inter process communication is concerned, there's really a lot of ways to do it.

The TCP channel approach is easy, but not very efficient if you want speed.
I haven't tried the shared memory approach myself, but this might be the way to go.. provided you can implement a secure and solid locking mechanism.

Another approach that has my interest is to use an SQLite database. It supports concurrent connections (built-in locking mechanism) and does not need installation of separate database software... You can embed it in your own app. It's also pretty fast.
Ofcourse your data structures need to be optimized for a relational data model.


Winni(Posted 2008) [#24]

Another approach that has my interest is to use an SQLite database. It supports concurrent connections (built-in locking mechanism) and does not need installation of separate database software... You can embed it in your own app. It's also pretty fast.
Ofcourse your data structures need to be optimized for a relational data model.



And that is even more inefficient than the TCP channel approach when you want speed, and probably even more error prone. SQLite might be fast, but processing a SQL query takes definitely more time than pumping a few bytes over a local TCP channel.

Since you could not use SQLite's :MEMORY: 'database' in a concurrent scenario, you'd have to use a database file, and file access is by far the slowest operation you can perform on a computer.

Furthermore, SQLite is not optimized for concurrent connections in general, because it is not a database server, and its documentation specifically does not recommend the use of SQLite in a (heavily) concurrent environment. This is where you'd use something like PostgreSQL or MySQL instead.

So while technically possible, given the choices in BlitzMax, I think TCP channels are still the safest option (having done my share of multi-threaded programming, I know how fragile the shared memory approach is and that you will always shoot yourself in the foot without proper synchronization; you just CANNOT know when and which thread tries to access the shared memory).


Jim Teeuwen(Posted 2008) [#25]
mm good point. The constant filesystem access is a pain in the ass.
I'm not too worried about the limited amount of concurrent connections in SQLite. The application in this scenario is not intended to serve as a webserver or something. Dealing with delegating processes like image manipulation and what not, your program should never exceed a handful of parallel processes. Any more than that and you'd have a serious design flaw.

I suppose in the end it's a trade-off matter. How dependent is your application on performance vs how much effort and potential headache are you willing to put in.

I still believe the TCP approach is the best solution/tradeof.


Kurator(Posted 2008) [#26]
anyone see a chance to get this library working with bmax?

it offers some pretty well optimized threading constructs (parallel_for i.e.) and also threadsafe datastructures (hashmap i.e.)

its multiplatform and open-source (lgpl):

http://www.threadingbuildingblocks.org/


*(Posted 2008) [#27]

So while technically possible, given the choices in BlitzMax, I think TCP channels are still the safest option (having done my share of multi-threaded programming, I know how fragile the shared memory approach is and that you will always shoot yourself in the foot without proper synchronization; you just CANNOT know when and which thread tries to access the shared memory).


Cant you just have a mutex for the functions that use a data structure.


Retimer(Posted 2008) [#28]
So do we really need the added complexity of threading for a language primarily intended for games?


I guess not if we want to write a project in multiple languages just to get a game server going.

When I bought blitzmax, I mistakenly took blitz as a worthy language for present-future, as obviously many of us shouldn't have. Threading is the future, even in games, and especially in even semi complex servers.

I'm using RakNet so i'm not limited by blitz tcp crapness, but threading can come in handy for damnwell anything. I just want it to split the work of timers and logging, as I have in test c# apps without so much "complexity". It's not difficult if you pay attention.

The language is so capable of competition, but everytime I flush the toilet and watch a brownie twirl around, it reminds me of where blitz is going because of lack of support where mark could probobly impliment things so easily that would keep blitz up and about for Many of us unsatisfied customers, with a few real updates.

Threading itself would make most of us frowny faces turn upside down, but that's not going to happen. This topic gets brought up every couple weeks with no result or even response. And because of this i've had to apologize to people that I recommended that purchased blitzmax, as they too are running, or going to run into dead ends.


ImaginaryHuman(Posted 2008) [#29]
There are dead ends everywhere it's just a matter of whether it impacts your project or not, no matter what the language. I do agree there could have been more updates with more features added but we can't have everything, so many people want BlitzMax to be BlitzMax3D that all other smaller features including threads are on the backburner. I think Mark intends BRL to stay a very small company with very few people, kinda casual. But what do I know.

There are probably a number of people on these boards who want threading who actually could figure out how to make it work themselves and write some third party solution but they aren't doing it.


*(Posted 2008) [#30]
why write a third party solution when the competition has threading already.


Brucey(Posted 2008) [#31]
From what I've seen on the forum, some of the people who complain about a lack of threading wouldn't even have the skills to implement any code using threading. (Obviously not aimed at those who know they have the skills).

So, we get threading, and 10 people can use it... great! :-p


markcw(Posted 2008) [#32]
Ok, so even though you can write a cross-platform game/app in BMax as it stands, threading would still be very helpful for some people and would keep them up to date with the PC times. Point taken.

So would it be possible to create a threading module for BMax? Like just pop a magic folder somewhere and it works? Or would it mean editing files that only BRL have access to?

Maybe the community could collaborate on a threading project.


plash(Posted 2008) [#33]
So, we get threading, and 10 people can use it... great! :-p
But don't forget the use it would have in Max3D and other BlitzMax modules/internals that BRL manages.

Overall, I would say it is laziness that causes issues like this. Like ImaginaryHuman stated, "There are probably a number of people on these boards who want threading who actually could figure out how to make it work themselves and write some third party solution but they aren't doing it."

I personally don't have a use for multi-threading, yet. Though it is nice to know your language has features you may want/need to use in the future.


ImaginaryHuman(Posted 2008) [#34]
I'm not exactly sure how multithreaded code looks to the developer, and I don't know if that many people would understand how to use it. Would we just define some block of code or a function that gets to run as its own thread?

I think we can do okay using processes so maybe that's something to build on. From what I can tell or have read there need to be changes to BlitzMax itself in order to make threading work `safely`. That said I would think there are at least some basic features that the language could offer, whereby it's not 100% thread safe but safe enough to do like 1 asynchronous file read or something like that, something useful.


degac(Posted 2008) [#35]

From what I've seen on the forum, some of the people who complain about a lack of threading wouldn't even have the skills to implement any code using threading. (Obviously not aimed at those who know they have the skills).

So, we get threading, and 10 people can use it... great! :-p


This is my impression too, and I'm one of the other thousand people that can't use multithreading.

Of course if multithreading is 'hidden' in BlitzMax itself (= common user doesnt' know that some functions work in multithreading) this will be great. But I still have some problems to understand 'where & when' (in normal conditions/applications) multithreading is useful.
And the final problem is the 'design' approch of an multithreading application.

I've read many threads where people complaing about GC safe in case of multithreading. I am completely noob in this programming field, but it seems (some) people want BlitzMax controls and resolves every possible scenario.
Take this simple example
type mytype
     field myfield:value
end type
Local a1:mytype 'not defined
a1.myfield=10 ' error -> object not defined -> stop

This example is not multithreading/multicore or any special: but in any case if the USER/PROGRAMMER skips a simple command (create the object) the program itself bomb out. This is not a BlitzMax fault.
In case of multithreading (I suppose) this kind of errors will be at the power of two: you could create an object in a thread A, using it in another one B, deleting it in the thread A and the thread B bomb out.
I think this is not a problem of BlitzMax itself! It's bad programming!

From my point of view (I repeat - I'm not expert in this field!!!) the most logic solution is to have a MAIN_THREAD (like now the actual BlitzMax) where 'shared-common' objects are defined/created (like a GLOBAL variable) and CHILD_THREAD where you can access only the 'main-thread-shared' object to read/write (and these cannot be freed by desing); everything is created in the child_thread should be destroyed at the end of the process of the child_thread (like a Local variable in a function).

In other words: in the main_thread there are all the 'resources', the child_thread can only work on them - but they cannot destroy them.
Maybe this approch is limited, but I think that the real problem (in a multithread scenario) is in the desing of the application.
This should be (on the paper and on my mind!) quite secure.

Repeat, I'm one of the other thousand people that can't use/understand multithreading...


Kurator(Posted 2008) [#36]
Handling with threads could be simplified, i.E. take this example from C# which uses the Parallel Extions Library from MS and ist quite similar to Threading Building Blocks from Intel:

                    Parallel.For(0, height, j =>
                    {
                        PixelData* outPixel = fastOut.GetInitialPixelForRow(j);
                        PixelData* startPixel = fastStart.GetInitialPixelForRow(j);
                        PixelData* endPixel = fastEnd.GetInitialPixelForRow(j);

                        for (int i = 0; i < width; i++)
                        {
                            // Blend the input pixels into the output pixel
                            outPixel->R = (byte)((startPixel->R * blend) + .5 + (endPixel->R * (1 - blend))); // .5 for rounding
                            outPixel->G = (byte)((startPixel->G * blend) + .5 + (endPixel->G * (1 - blend)));
                            outPixel->B = (byte)((startPixel->B * blend) + .5 + (endPixel->B * (1 - blend)));

                            outPixel++;
                            startPixel++;
                            endPixel++;
                        }
                    });


The secret lies in the library, the library knows:
- how many cores are availaible
- which granularity to use

It creates itself the ideal amount of threads an speeds up near to linearity. To use parallelis in constructs, where it could easily done - is much more error safe and easier to develop like an "real" threaded game where you have an extra renderthread, ai-threa, physics-thread, io-thread....


*(Posted 2008) [#37]
PureBasic has:
Thread = CreateThread( @MyProcedure(), 0 )


thats it


markcw(Posted 2008) [#38]
It's a little more than that actually:
http://www.purebasic.com/documentation/thread/index.html


degac(Posted 2008) [#39]

PureBasic has:
Thread = CreateThread( @MyProcedure(), 0 )




I dont' know PureBasic syntax, but it seems that this command 'starts' a Procedure (=Function in BlitzMax) as a separate thread. If so the Procedure-thread has or not access to the 'global' resource - and on the other side - this procedure can 'returns' any value? Are there something special to declare a variable 'shareable' with threads?

In case of threading support what are/should be the modules that must(can) be rewritten to take advantage (if any) of this?

Another question: threads and cores.
Is the OS that manages threads on cores or not? I still don't know this aspect: imagine tomorrow to have BlitzMax with threading support running on a 4 core CPU and using only 1 core...

Just for curiosity...


ziggy(Posted 2008) [#40]
I would vote for a simple parallels multithreading. that would be enough in most situations and I supose it's easier to implement and mantain.


JoshK(Posted 2008) [#41]
I can use multithreading right now for Newton. We could be using up to eight times the physics interactions we can do now. It's too bad, because our engine is perhaps one of the best showcases of Newton physics, but BlitzMax compromises my ability to use it to its full potential.

Other applications are a little more theoretical and difficult to implement. The CPU side of our renderer, where the visible objects are determined, is a task that could be parallelized, and it would not be too complicated, because the data exchange is pretty simple. It just writes the instance matrices to arrays, and those can be made into separate arrays for each thread.

If you are writing any kind of application that processes a lot of data, like a lightmapper, you should not use BlitzMax, because it will be a little more than 25% the speed you could be getting. For the highest-end hardware people have right now, you will be settling for about 12.5% the speed you could have otherwise, assuming a scaling of 100% performance per core. 8 cores, that's insane! Well, no it isn't. A year ago having 96 stream processors on a GPU was a lot, and I am getting ready to trade in for a new card with 480 stream processors.


ImaginaryHuman(Posted 2008) [#42]
It's hard I think to understand how you would write ordinary (and indie) game code that could take advantage of 8 or more cores. I mean, either you want the language to do that part for you or you have to manually design your code that way.

The o/s does take care of which threads end up being run on which CPU and/or Core. It doesn't really matter if your hardware has multiple individual CPU's and/or multiple individual cores within those CPU's, for example the top of the line Mac has multiple multi-core CPU's. The o/s is the part that will deal with knowing which cores are in use for what and will allocate your thread or process to run on them as it sees fit - but it doesn't really know how to split up your program into threads which is why the language or the program itself has to do that.

I agree with Leadwerks that things like physics and other high-computation stuff could be taking much better advantage of better CPU's. Chances are users with a high powered GPU are likely to have high powered multi-core CPUs also.

The part of the Blitz garbage collector that seems to be a problem is the reference counting, whereby if it reads a count and then tries to modify it another thread could be modifying the same count at the same time and one of their calculations would get lost, resulting in an incorrect count and thus memory leaks. But i don't see why that is so difficult to fix, it's not exactly a difficult problem.


*(Posted 2008) [#43]

I dont' know PureBasic syntax, but it seems that this command 'starts' a Procedure (=Function in BlitzMax) as a separate thread. If so the Procedure-thread has or not access to the 'global' resource - and on the other side - this procedure can 'returns' any value? Are there something special to declare a variable 'shareable' with threads?


Basically with PB you have CreateMutex() then where the data is shared you use LockMutex and UnlockMutex afterwards in the Thread this stops the thread from corrupting the data in the type etc.


In case of threading support what are/should be the modules that must(can) be rewritten to take advantage (if any) of this?


Off the top of me head networking would be a good one, come graphics maybe and a dedicated threading module is a must.


Another question: threads and cores.
Is the OS that manages threads on cores or not? I still don't know this aspect: imagine tomorrow to have BlitzMax with threading support running on a 4 core CPU and using only 1 core...

Just for curiosity...


I dont see a reason why multithreading would not be possible on multicores using more than one core. The bigger question is with all the farsight with Max why wasnt it implemented in the first place.


Winni(Posted 2008) [#44]
On all modern systems, it -is- the OS that distributes and automatically load balances the threads over multiple cores -- unless the application defines an 'affinity' for the thread to a certain CPU/core. In Windows Server's (or NT's or XP's or Vista's) task manager, you can also observe how a CPU heavy thread or process wonders from one core to another, so that not one and the same CPU is burning out.



The bigger question is with all the farsight with Max why wasnt it implemented in the first place.



That, indeed, is the question. Especially since all other programming languages have been supporting multi-threading in one way or the other since the late 90s.


ImaginaryHuman(Posted 2008) [#45]
"I dont see a reason why multithreading would not be possible on multicores using more than one core. The bigger question is with all the farsight with Max why wasnt it implemented in the first place. "

When BlitzMax was being developed, let's say 4-5 years ago, I don't think multiple cores were very commonplace so it probably wasn't a big factor to think about at the time.


*(Posted 2008) [#46]
ImaginaryHuman: I agree multicore processors werent common for desktops the main thing that I wonder is threading has been in there for years surely they could have been implemented.


JoshK(Posted 2008) [#47]
On all modern systems, it -is- the OS that distributes and automatically load balances the threads over multiple cores

I am aware of that, but the CPU attempting to accelerate a linear program is not going to anywhere near as efficient as running multiple threads.


Ian Thompson(Posted 2008) [#48]
Each process has their own single thread, the O.S allocates it. Thats the real problem, if I want two threads for two seperate functions within my program for example, I would need two seperate programs... which is not ideal.


Winni(Posted 2008) [#49]
Yes, Ian, but this is only a BlitzMax problem. If you use another language that supports multi-threading, the OS would distribute those threads over multiple cores if they are available.

ImaginaryHuman, even four or five years ago, multi-processing was already coming and an already old standard for servers. I had my first SMP board with two Pentium I 100 MHz chips in my desktop computer back in... I don't even remember. 1996 or so? It was when NT 4.0 was still in beta.

But may that as it be, you don't only benefit from multi-threading when you have multiple cores. Multi-threading is also a great way to keep an application responsive while it is processing data in separate threads - even when you only have one CPU core available.

And even four or five years ago it was already obvious that multi-processor systems would take over the market. We've had this discussion before here, and when we look at the current developments, then it is already obvious that in a few years from now systems with 8 or even more CPU cores will be the --minimum-- hardware that you --can-- purchase - as it is already almost impossible today to still buy a single core system. Dual Core CPUs have entered the low end market and for christmas we will see the first wave of Quad Core --notebooks--. And singe CPU cores won't get much faster than they already are now, which is the reason why the designs have changed to multi-core architectures in the first place.

This trend has been discussed in the press and by the chip manufacturers years ago, everybody knew it was coming. Even Microsoft designed C# and .NET to be multi-threaded from top to bottom (years before BlitzMax was conceived). And Microsoft does not necessarily have a reputation for its foresight. ;-)


plash(Posted 2008) [#50]
Maybe we could use a dll and global variables to pass information to our sub processes.

http://www.blitzbasic.com/Community/posts.php?topic=66616#744727

We would have to share the function/variable pointers and/or maybe just the dll handle.


Winni(Posted 2008) [#51]
I think Named Pipes and/or Events sent to a specific process are the best alternatives to socket-based communication, and those are supported by all OS for which we have a BlitzMax compiler.


Htbaa(Posted 2008) [#52]
It could be that I missed it somehow in the forums. But I was just checking the latest SVN updates and found the existence of a experimental threads.mod module.

I don't know if it works, or currently how it works (have never used multi threading), but a experimental module is there.


ImaginaryHuman(Posted 2008) [#53]
So check it out and find out what it does and let us know ;-D


Mark Tiffany(Posted 2008) [#54]
but a experimental module is there

Remember folks, SVN version is unstable (but normally pretty darn good) dev code... ;-)


Htbaa(Posted 2008) [#55]
I know it's unstable, or might not even work yet. It's just good to see they're working on it :-)


REDi(Posted 2008) [#56]
Nice find Htbaa, I just spent about half hour getting it to do its thing, and it seems the module is coming on a treat :)
Now I take it we've got to wait while they take on the massive job of making the language thread safe?

Good luck Mark and co! This could be the start of something special! :)

HOW TO GET IT WORKING:
*EDIT* Snip, See Bruceys instructions below! YAY!


DStastny(Posted 2008) [#57]
Thats too funny, I pointed out in a thread two years ago that Mark should try that collector http://www.blitzbasic.com/Community/posts.php?topic=62001#693207

Nice to see an attempt. Has the debugger been changed to be thread safe? I will have to take a peek. Might make me want to use Max again.

DStastny


REDi(Posted 2008) [#58]
So far it seems to be working very well!

:D YAY! *THANK YOU BRL!*


Htbaa(Posted 2008) [#59]
That's very good to hear. Although I think the GC will need a lot of testing wouldn't it?


Mark Tiffany(Posted 2008) [#60]
That's very good to hear. Although I think the GC will need a lot of testing wouldn't it?


...then download a gc lib from...


Yes, the new threading stuff requires the use of a thread safe GC: hence mark has plumped for using an existing open source one that you currently need to download separately, rather than rewrite his own. Which seems a wise choice to me.


Snixx(Posted 2008) [#61]
Ok so Ive downloaded from the SVN and all that stuff, can I get an example of createthread usage etc please? just something very simple would be fine.


Azathoth(Posted 2008) [#62]
for anyone interested (or just nosey like me), you'll need to enable threading in bmk_config.bmx, rebuild and place bmk.exe into the bin folder
Isn't that what the -h option is for? And shouldn't the new SVN bmk.exe already contain the compiled bmk_config.bmx?


*(Posted 2008) [#63]
Brilliant stuff will definately be waiting for this, its the last thing that BlitzMax needs =)


Htbaa(Posted 2008) [#64]
Well, the last thing might be the option to overload operators :-). Would make Vector stuff a lot easier.


REDi(Posted 2008) [#65]
Isn't that what the -h option is for?


Yes you're correct Azathoth, but I find it a lot easier than messing about with the command prompt *every time* I wanted to compile something to test, I just assumed anyone who would use the prompt would spot that while looking at bmk_config.bmx.

Horses for courses I suppose, anyway YAY THREADS!


Winni(Posted 2008) [#66]
Why didn't BRL announce this?

Anyway. I'm currently checking it out from SVN.

Do we need to rebuild the modules with this switch for the thread module to (safely) work?


degac(Posted 2008) [#67]

Why didn't BRL announce this?


Maybe because it is still a work in progress (experimental module).
And of course
- a new MaxIDE with support for the new parameter H (threaded) is needed;
- testing of the collateral effects...
- time to write a 'basic' tutorial & some documentation

Some of us are waiting multithreading support for years, I think they/we can wait until BRL/Mark finishes the job. Asking now is premature (in my opinion).


Space_guy(Posted 2008) [#68]
This is fantastic news. I hope ill get some time soon to check it out


Brucey(Posted 2008) [#69]
Why didn't BRL announce this?

Because it's not been "released" yet? :-p


Winni(Posted 2008) [#70]

Because it's not been "released" yet? :-p



Yeah, I bet that's why Microsoft announced Vista more than five years before its actual release. ;-)

We can only help BRL with all the beta testing when we -know- that there are new features that need testing (and documenting and sample writing).


Brucey(Posted 2008) [#71]
Fair enough...

Okay, the basic steps you'll need to take are :

1) Get your code in sync via SVN. The new bcc and bmk will be kind of important for this stuff to work.

2) Download the GC source from here

3) Copy the extracted folder to blitz.mod and rename it to 'bdwgc' so you get blitz.mod/bdwgc/include/...etc...

4) Edit blitz.mod/bdwgc/include/gc_config_macros.h and add this line at the top (to enable threadsafe GC):
#define GC_THREADS

(this is kind of important, so you really don't want to forget this).

5) Make sure you've done No. 4 !

6) run : bmk makemods -a -h
The -h flag builds a set of modules with the new GC/thread stuff. You'll notice that the compiled modules have a .mt. in the name.


That should be you sorted.
Obviously, you kind of want the IDE to have the flag-setting ability too. Or, run it from the command-line.

Remember it's not "official" yet, so if it blows up your code, you shouldn't be surprised. ;-)

This GC also handles cyclic references, apparently - according to the docs - but you'll probably need to give that a thrashing just to be sure.

:o)


Htbaa(Posted 2008) [#72]
If this GC can handle circular references that would be very nice. Takes away the need to clean up stuff myself and do heavy testing if it's actually being removed.


JoshK(Posted 2008) [#73]
Interesting. I have some uses for this when it is ready.


REDi(Posted 2008) [#74]
Cool thanks Brucey!

I wonder how its going to work when a module is written using threading? will it force anybody using the module to compile with threading enabled?

anyyway,YAY THREADS AGAIN


Brucey(Posted 2008) [#75]
I suppose you could wrap the module in a

?threaded

block, like you would with ?win32 etc, in which case when they build it non-threaded there'd be nothing compiled.


Yan(Posted 2008) [#76]
I've quickly hacked support into the CEIDE (v2).

If anyone's interested, here's the patch...

...Hopefully the flakey code box hasn't buggered it up.


I wasn't quite sure how to handle building modules, so the 'threading support' state indicates whether to build threaded or standard modules.

Oh yeah...The 'threading support' state isn't saved and always defaults to false, I thought this was the safest route for now.


Azathoth(Posted 2008) [#77]
Is this GC going to replace the Blitzmax GC in non-threaded programs too?


Brucey(Posted 2008) [#78]
This GC is slower on older machines than the BlitzMax GC. On my work's 2ghz P4, it is much slower.
On more modern processors, it runs great.

btw, just because it is a "threaded" build, it doesn't mean you need to use any threading stuff in it. Perhaps you just want to use the new GC.


Winni(Posted 2008) [#79]

- a new MaxIDE with support for the new parameter H (threaded) is needed;



Here's the good news: The MaxIDE version from SVN already supports this switch. ;-)


Winni(Posted 2008) [#80]
So what parameters does CreateThread() expect?

My funniest compiler error so far was "Unable to convert from 'Object(Object)' to 'Object(Object)'".


REDi(Posted 2008) [#81]
It expects a function pointer...
Local Thread = CreateThread(ThreadProc,"hello")
Delay 50000
DetachThread(Thread)

Function ThreadProc:Object(data:Object)
	Print String(data)
	Local c:Int
	Repeat
		Delay 500
		c:+1
		Print c
	Forever
End Function



Winni(Posted 2008) [#82]
Thanks! I get something working now.

So I cannot pass a method to CreateThread directly; instead, it must be a global function of type object that expects exactly one parameter of type object.


Winni(Posted 2008) [#83]
A sample for coffee drinkers: Ten developers and one coffee machine. Simple text-only version.




ImaginaryHuman(Posted 2008) [#84]
This looks cool. Nice (and funny) example, Winni. I am not one of those new fangled SVN users so hopefully this will be all nicely tested and documented and integrated into the IDE etc and released in some official Max update soon for us other users :-) Good to hear they've been working on this and that it's actually working, way to go BRL/Mark.


Winni(Posted 2008) [#85]
Thanks! ;-)

I think BRL is providing a great service with their SVN repository; it really is worth to regularly take a look at it.

By the way, I think this version of the sample is nicer, but it requires MaxGUI:
Download for Mac OS X Intel.
Download for Mac OS X PPC.
Download for Windows.




ImaginaryHuman(Posted 2008) [#86]
How can you tell how many threads are actually being created and used and that doing so is providing more CPU performance than without, ie on multicore machines?


xlsior(Posted 2008) [#87]
Hm.... I followed the steps Brucey specified in his posting, but I can't seem to get it to work...

Even though CreateThread and CreateMutex are syntax-highlighted in the IDE, both of those commands generate an 'identifier not found' when I try to run the samples on this page.


Winni(Posted 2008) [#88]
Well, you're in control of how many threads you create. If threading makes a program actually faster depends on what you are doing.

For example, if you want to convert four Jpegs to TIFF format, then it will certainly be faster if spawn four worker threads for that task on a Quad Core machine than having the images converted sequentially on just one CPU core. You usually won't get four times the speed in that special hardware scenario because of administrative overheads and I/O synchronization issues, but it -will- be measurably faster.

On a single core CPU, the multi-threaded approach will probably be a bit slower than a single-threaded implementation (because of the additional overhead to handle the threads).

The coffee example above does nothing useful. It's a remake of an old (from before 1998, actually) Alaska Software Xbase++ sample, by the way, that my ex-colleague Dr. Hannes Ziegler had written to demonstrate thread synchronization issues.

The sample spawns ten threads, one for each developer, and they share one resource: The coffee machine (which here also serves as a cupboard, hmm). The coffee machine's mutex makes sure that only one developer at a time can use it, the others have to wait until the mutex is unlocked.

The problem with threads is that you usually cannot determine when it gets a time slice from the operating system or in which order the threads are executed. And you enter the field of complete chaos when you begin fiddling with thread priorities. If you don't want your app to crash or show unpredictable behavior, you -need- to synchronize the access to shared resources.


Winni(Posted 2008) [#89]
xlsior, you need to tell the IDE to enable Threading in "Build Options."


MGE(Posted 2008) [#90]
"The problem with threads is that you usually cannot determine when it gets a time slice from the operating system or in which order the threads are executed. "

Does this make it useless for actual game design?


Winni(Posted 2008) [#91]
I think that's one of the reasons why multi-threading has only been used quite rarely in game development so far.

Tim Sweeney (Mr. "Unreal" engine) has said some interesting things about this subject, for example here.

Multi-threading and multi-processing are certainly not useless, but just more difficult to handle (at least for game development).


xlsior(Posted 2008) [#92]
xlsior, you need to tell the IDE to enable Threading in "Build Options."


...

Duh.

Ok, that did the trick. Thanks for the pointer!

So... Anyone know whether or not it's possible to (pre)load images in the background using a thread and still have them be accessible to the main program once it's done?


degac(Posted 2008) [#93]

you need to tell the IDE to enable Threading in "Build Options


mmm...are you sure? I have downloaded from SVN (from /dev/win32_x86 and from /v130/win32_x86/bin) but I can't find nothing under 'Program/Build options'...

Well, I'll wait for an official release :D


xlsior(Posted 2008) [#94]
mmm...are you sure? I have downloaded from SVN (from /dev/win32_x86 and from /v130/win32_x86/bin) but I can't find nothing under 'Program/Build options'...


You'll need to recompile the IDE for the new option to show up.
You can find the source in /src/maxide/maxide.bmx (MaxGUI required)


degac(Posted 2008) [#95]
ah! now it make sense!
It's very experimental...
Thank you!


Snixx(Posted 2008) [#96]
With threading enabled in the ide I get a build error... all mods have been rebuilt and are latest from SVN, bmk has been recompiled and replaced with the new one.


Brucey(Posted 2008) [#97]
With threading enabled in the ide I get a build error

What is it exactly?


REDi(Posted 2008) [#98]
What's the build error?

*EDIT* DOH should have refreshed before posting ;)
... YAY, THREADS!


Snixx(Posted 2008) [#99]
nowt, just "build error" and sometimes "command line error",


REDi(Posted 2008) [#100]
Snixx, just a thought but what platform are you on? AFAIK its windows only ATM.

*EDIT* scrub that, just checked and it is cross platform.


Snixx(Posted 2008) [#101]
Vista home premium 64bit sp1

I did get the entire lot from the SVN and recompile it all and remake the bmk exe n all that, still not alot of luck.


REDi(Posted 2008) [#102]
NOTE: It doesnt seem to like Try+Catch used in the threads.

Snixx, I'll give it a go on Vista x64 later, when I've got some spare time.


Space_guy(Posted 2008) [#103]
Wow. i must have done something seriously wrong in either my engine or the installation but when running it thread enabled resulted in a framrate 0-2 fps from arround 150 when run unthreaded.

although I will make a new engine and quite possibly correct what ever the new gc doesnt like it was surprising to get such a loss in framerate from just switching gc


Mark Tiffany(Posted 2008) [#104]
I did get the entire lot from the SVN and recompile it all and remake the bmk exe n all that, still not alot of luck.

Did you download bdwgc and install that as per Brucey's instructions above?

but when running it thread enabled resulted in a framrate 0-2 fps from arround 150 when run unthreaded.

Because threaded code is "costly".

Mark's original garbage collector was deliberately simple (based on a simple count of object references) for speed in a games programming language. To enable threads, he has used a pre-existing (i.e. not his own) thread-safe garbage collector which is simply imported, and is by necessity much more sophisticated (e.g. it can deal with cyclical references, etc), therefore it is bound to run slower than mark's original ref-counted GC. Admittedly that difference is rather drastic in some applications at present, but this is experimental still.

Threading will not magically speed things up - it will probably magically slow things down, until you start writing good code to explicitly make appropriate use of it. And that is the tricky bit...


Snixx(Posted 2008) [#105]
trying to makemods with -a -h does nothing at all...


Space_guy(Posted 2008) [#106]
yeah. i did expect a drop of performace yes. but in this case it its just abnormal. somewhere probbably i did some mistake in the compiling of the new gc or i do something that is really alot more costly that i can do differently. ill find out eventualy :) anyway im enjoying threads so far.


Snixx(Posted 2008) [#107]
ah wtf Building main
Compiling:main.bmx
Build Error: failed to compile C:/Projects/Project 1/main.bmx
Process complete


blah blah, followed the instructions perfectly, ah well


Winni(Posted 2008) [#108]
Snixx, when you run into that problem, then you most likely did -not- put Boehm's Garbage Collector in the appropriate directory.


2) Download the GC source from here

3) Copy the extracted folder to blitz.mod and rename it to 'bdwgc' so you get blitz.mod/bdwgc/include/...etc...

4) Edit blitz.mod/bdwgc/include/gc_config_macros.h and add this line at the top (to enable threadsafe GC):
#define GC_THREADS



When you are on Windows, the GC should go into C:\Program Files\BlitzMax\mod\brl.mod\blitz.mod\bdwgc

On OS X, it's /Applications/BlitzMax/mod/brl.mod/blitz.mod/bdwgc

(Unless, of course, your BlitzMax directory is somewhere else.)

Make sure that you add the #define pre-processor statement as instructed by Brucey.

Only when the new GC is in exactly the right directory, and only when you checked out the latest BCC and BMK tools, bmk makemods -h -a does work.

And only after that has finished, you can compile the new MaxIDE with Threading support.

Maybe the best way is to checkout from SVN to a new and empty location (that's what I did).

But the new stuff really -does- work. At least on OS X; I didn't bother to play with the Windows and Linux versions.


Brucey(Posted 2008) [#109]
Maybe the best way is to checkout from SVN to a new and empty location (that's what I did).

or... wait until it becomes an "official" release and there's nothing out of the ordinary that needs to be done to use it?

I would class things like this as "advanced" :-p


Snixx(Posted 2008) [#110]
ah where does bcc compile from, that might be why :P


Brucey(Posted 2008) [#111]
bcc you download from the SVN site... bin/bcc.exe


*(Posted 2008) [#112]
I will wait for it to become a 'official' release as I want it to work on as much as possible without too many problems. TBH threading will make Max as complete as I need it to be and I will definately be considering it a good language to create things on in the future when this is official :)


Winni(Posted 2008) [#113]
Waiting for it become official certainly is a safe thing to do. Only that nobody knows when that will happen; official releases - and that's where SVN does not count - have been very slow over the last year.

I noticed another interesting thing while I was playing with MaxGUI and Threading yesterday; the new MaxIDE threw out -tons- of Cocoa-warning messages about gadgets not being part of an NSAutoReleasePool and so on. Also, MaxIDE fired up the cooling fans of my Mac Pro after a short while (while the standalone app did not). Weird.


Snixx(Posted 2008) [#114]
ah well that was it, but still I have a problem with "cannot find interface for module 'brl.blitz' (compile error)


Winni(Posted 2008) [#115]
Maybe you should try and take the long walk: Perform a complete HEAD checkout from SVN to an empty directory. That way, you're guaranteed to not have any version mixups and conflicts and you're sure you have the latest and greatest.

But did you rename the GC directory according to Brucey's instructions and did you change the header file by adding #define GC_THREADS on top of it? And is the GC in a subfolder of blitz.mod, and is that subfolder called bdwgc?


Snixx(Posted 2008) [#116]
Completely new folder, followed exactly, added the define, using the new bcc, bmk, makemods worked with -h -a, recompiled the ide... I will just leave it at that:D and wait for the official version.


Winni(Posted 2008) [#117]
Can you post a screenshot of your directory structure, check again whether MaxIDE has the Threading switch turned on, copy and paste the complete error message here and maybe even show what source code you are trying to compile?

I'm convinced we can get it running. ;-)


REDi(Posted 2008) [#118]
SVN update available! it now includes the new GC source. :)


Yahfree(Posted 2008) [#119]
http://blitzbasic.com/Community/posts.php?topic=80344


Czar Flavius(Posted 2008) [#120]
Threading itself would make most of us frowny faces turn upside down, but that's not going to happen. This topic gets brought up every couple weeks with no result or even response. And because of this i've had to apologize to people that I recommended that purchased blitzmax, as they too are running, or going to run into dead ends.
HAHAHA YOU LOSE


Retimer(Posted 2008) [#121]
Glad I was proven wrong =P

This is honestly the first time I have seen a developer come through like this after such a dead period, so i'm still in shock at the moment. And you can't fully blame me as there wasn't any real communication to the community about this lol.


xlsior(Posted 2008) [#122]
This is honestly the first time I have seen a developer come through like this after such a dead period, so i'm still in shock at the moment.


It's really not that unusual for BRL -- in the past, similar "out of the blue" additions (after many repeated requests and a long silence) were:

- Addition of DirectX (originally BlitzMax was OpenGL only)
- Addition of Intel Mac compatibility (At the initial release of BlitzMax, apple was still PowerPC-only. Shortly afterwards Apple surprised the world with their "we're moving everything to Intel" announcement. BRL must have loved that, considering how much work they must have put into creating the PowerPC version in the first place)

Furthermore: If you're set up to sync from SVN, you'll find that there is a fairly steady stream of minor changes, additions and adjustments. While major features like threading don't come along every day, I'd hardly consider it "dead". It's just that BRL seems pretty quiet about all the ongoing changes until they drop a bomb every once in a while.


markcw(Posted 2008) [#123]
Hold on a second...
Try it before you buy it, but don't complain after.

Is this your mantra? If so you don't live up to it. :)


Retimer(Posted 2008) [#124]
This topic gets brought up every couple weeks with no result or even response


It was actually targeting the people complaining about it for the most part, since there was/has/is already alternatives. You'll also find other posts of me referring to my quote on many of the complaints, instead of complaining. Regardless, i'm a huge blitzmax fan, and this addition will save me a lot of time and money of not having to reprogram my servers.

It's really not that unusual for BRL


I've only been around blitz less then a year, but from the work logs I read, I could only assume from the information that was right infront of me (none), and what the blitz elitists from the start were giving, along with my history with plenty of software with high claims and no show (As where my quote comes from, Markcw).

But if complaining gets things done....forum mailbox/PM feature so posts like this don't bring things off-topic, and other topics won't have to get locked =P


Brucey(Posted 2008) [#125]
What's a blitz elitist look like?


Retimer(Posted 2008) [#126]
Kind of like Bruce Lee with glasses


markcw(Posted 2008) [#127]
More like Flameduck with glasses.


Winni(Posted 2008) [#128]
Or like Zeus on Mount Olympus, holding a bunch of thunderbolts in his hand, ready to throw them down at the mere mortals :)


SebHoll(Posted 2008) [#129]
Locked!

Threaded BlitzMax!