All access to the Castle Game Engine API must happen from one and the same thread.
We address various threads use-cases by introducing instead asynchronous operations, which is a fancy way of saying that you still use engine from one thread, but you don’t need to wait (block) for some operations to finish, instead you get a callback when they are finished. Such asynchronous API is both easier to use and easier to implement.
Some Castle Game Engine code or the Pascal libraries we depend on internally uses Pascal TThread
. You generally don’t need to be aware of it, everything just works automatically.
Note
|
When using FPC on Unix, we need to use CThreads to enable threading support. Our program templates already do this, so in the typical case you don’t need to worry about it. |
Example places that use threads from Pascal:
Unpacking streaming music (TCastleSound.Stream
)
Asynchronous downloading (TCastleDownload
)
Kraft, our physics engine, may also use threads
Other (non-Pascal) libraries underneath naturally also may be use threads for rendering, audio playback and more.
Important use-case for using threads:
The usual use-case for multi-threading in games is to have perfectly smooth rendering (of loading screen, or even of the interactive game) while at the same time loading some resources to GPU. This isn’t possible now using Castle Game Engine. Things like "loading screen" must be done in discrete steps now (see e.g. StateLoading
in examples/user_interface/zombie_fighter), and it is unavoidable that the process will just have to wait for some steps to finish, so your loading screen will not have 60 FPS.
Future:
Asynchronous loading will be possible some day.
This requires renderer capable of multi-threading.
Like Vulkan.
Or OpenGL, just with another context in another thread.
We prefer exposing asynchronous calls instead of just "allowing to use CGE from multiple threads". So you will still be required to call all CGE from main thread, however we will expose methods like TCastleScene.PrepareResourcesAsynchronously
that call some callback in main thread when the loading finished. This is a more limited approach, but also easier to implement (greater flexibility for the implementation, because we know that only specific things are done in the background — not that potentially everything is in different thread). It is also in practice better for some users — using threads is hard to do 100% correctly (without making any mistakes in data access, that result in very-rare-but-possible-and-painful-to-debug crashes).
Also TCastleScene.LoadAsync(..., OnFinish: TNotifyEvent)
. As with TCastleDownload
, it would internally use threads or not — this fact would be hidden from users. This approach gives simple API, allows engine to do it internally in various ways, and doesn’t force users to deal with dangers of multi-threading (similar to Unity async loading).
Notes about parallelism already possible/done in CGE:
Some parallelism is already happening, by rendering done in parallel (which is hidden from us by OpenGL), audio playing in parallel (which is hidden from us by OpenAL), some physics processing done in parallel (which is hidden from us by Kraft), and downloading in the background (which is hidden from us by TCastleDownload
). This approach, while more limited, allows to keep the engine simple and efficient, without worrying in every API call "what if some other thread is doing something with my data in parallel".
You can also of course use threads (e.g. through Pascal’s TThread
, threadvar
and other constructs) to do your own work, e.g. calculating something or loading files from disk to a stream. FPC standard library is thread-safe, in the sense that you can use e.g. one TFileStream
in one thread, another TFileStream
in another thread, without any need to synchronize (but remember they must be different TFileStream
instances). Just make sure to synchronize (i.e. call from the main thread) the place where you pass these TFileStream
contents to CGE.
Future challenges:
(SOLVED?) All OpenGL(ES) commands in the same context must be done from the same thread. This is a limitation of OpenGL(ES).
But, as I learned on GIC 2022, it is actually possible to load data into OpenGL from another thread. You just need a second context with sharing (and all contexts that CGE initializes automatically use sharing). So it should be possible to e.g. load textures to GPU while doing a smooth rendering from another thread.
Vulkan also allows to use multi-threading. Although the burden of protecting multi-thread access in Vulkan lies in the hands of user, so it’s not something trivial to take advantage of anyway, but at least it becomes possible. See:
Right now, some engine code uses global caches (for texture/audio data, for X3D prototypes, and much more). If we decide to make some of them thread-safe, they will have to be reworked (so, more complicated code and possibly a little slower) or be separate per thread (so, less caching -> less gains).
The CGE will probably never be thread-safe in the sense that "you can access anything from multiple threads simultaneously". This would require a lot of safeguards and code complications, more than we can realistically maintain. Again, software larger than CGE (like LCL or Unity) also didn’t dare to do this.
Maybe we can make (but even this is far future) some clear separation, e.g. being able to use classes like TCastleScene
, TDrawableImage
from separate threads, but only if you use different instances of them.
Although I’m leaning more toward only having specific operations asynchronous, like PrepareResourcesAsynchronously
, thus "hiding" threads in simple (but more limited, but also easier to implement) API. Thus, it is possible that access to CGE will remain limited to "only a single thread" forever, but we will expose APIs to do asynchronous loading of specific stuff like images or scenes.
To improve this documentation just edit this page and create a pull request to cge-www repository.