lin.xianming Posted February 25, 2017 Posted February 25, 2017 Hi, I observed an issue related with threads. The attached file --players.cpp-- illustrating the problem, which was modified from the sample named "players". Please replace the original one then you can find out the situation. Basically I try to enable or disable nodes. If I carry out the operation by main thread then everything is OK, Otherwise a strange situation happened: you can turn off the node, but can't turn on the node anymore. void Blink() { static int n = 0; static int bEnabled = 1; if (0 == ++n % 100) { bEnabled = !bEnabled; mesh_3->setEnabled(bEnabled); } } static ULONG WINAPI ExterThreadPro(LPVOID pParam) { AppWorldLogic* pAppWorldLogic = (AppWorldLogic*)pParam; pAppWorldLogic->Blink(); return 0; } int main(int argc, char **argv) { EnginePtr engine(UNIGINE_VERSION, argc, argv); AppWorldLogic world_logic; AppSystemLogic system_logic; system_logic.init(); world_logic.init(); while (engine->isDone() == 0) { world_logic.update(); if (FALSE) {//Everything is OK world_logic.Blink(); } else {//The strange situation happened: you can turn off the node, but can't turn on the node anymore. HANDLE hThread = ::CreateThread(NULL, 0, ExterThreadPro, &world_logic, 0, NULL); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); } engine->update(); engine->render(); engine->swap(); } return 0; } Players.cpp
lin.xianming Posted February 25, 2017 Author Posted February 25, 2017 Sorry for launching the topic twice, coz I forgot to attach the file at the first one.
maxi Posted February 25, 2017 Posted February 25, 2017 Hi Lin, Unfortunatelly, this is a known issue, you can enable node only from the main thread.
lin.xianming Posted February 26, 2017 Author Posted February 26, 2017 Hi Maxi, Thanks for your quick replying.Such kind of thread related problems are not only the "enable node" operation, If I want to modify vertices of ObjectMeshDynamic or call Console::get()->run("world_load xxx") from AUX threads instead of main thread, then similar situations take place. However, if I call Object::setTransform(), for example, it works perfectly.I am curious to know what kind of considerations cause the restriction. If it can be eliminated, then we'll get much more freedoms to organize the threads of our applications.Thanks again.
silent Posted February 27, 2017 Posted February 27, 2017 lin.xianming, Currently there are a lot of C++ API methods are not thread-safe (basically the internals are designed to work such way). We will try to improve over time our docs to mark thread-safe API methods. Sorry for the inconvenience caused. How to submit a good bug report --- FTP server for test scenes and user uploads: ftp://files.unigine.com user: upload password: 6xYkd6vLYWjpW6SN
lin.xianming Posted February 27, 2017 Author Posted February 27, 2017 Hi Silent, I fully understand to design a thread-safe C++ API is quite difficult. But I don't think those problems are associated with thread-safety. The opperations in AUX thread can't cause any conflicts with the main thread, because the main thread is blocked while AUX thread calls Blink(). if (FALSE) {//Everything is OK world_logic.Blink(); } else {//The strange situation happened: you can turn off the node, but can't turn on the node anymore. HANDLE hThread = ::CreateThread(NULL, 0, ExterThreadPro, &world_logic, 0, NULL); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); } I mean developers should arrange threads properly to avoid thread conflicts. On that condition, --Unigine--, as an Engine, why not give more freedoms to developers?
maxi Posted March 1, 2017 Posted March 1, 2017 Due to the internal limitations, some methods won't work or work incompletely outside of the main thread. I've found these: ObjectDynamic: flushVertex, flushIndicesObjectMeshDynamic: flushVertex, flushIndices ObjectWaterMesh: flushVertex, flushIndicesNode constructor, setEnabled, setCollider, setClutter
lin.xianming Posted March 16, 2017 Author Posted March 16, 2017 Hi Maxi, You have already expressed your meaning crystal clear. But I run into a stone wall. The top priority of my App is to maintain a stable frame rate (e.g. 60). The original strategy is to create multi-threads to take charge of readering and the rest of heavy tasks separately. For instance, while the App needs to create a sophisticated ObjectDynamic, the heavy loading job is done via an AUX thread, meanwhile keep the readering thread fly smoothly. Due to the internal limitations, some methods won't work or work incompletely outside of the main thread. I've found these: ObjectDynamic: flushVertex, flushIndicesObjectMeshDynamic: flushVertex, flushIndices ObjectWaterMesh: flushVertex, flushIndicesNode constructor, setEnabled, setCollider, setClutter Now It seems that I have to change my strategy, but so far I haven't figured out an alternative way. Do you have any advice?
ulf.schroeter Posted March 17, 2017 Posted March 17, 2017 Some thoughts based on my experience with Unigine 1 (in Unigine 2 things might be a little bit different, but I thing the general threading architecture should be quite similar):Most basis assumption ("mother of all multi-treading strategies"):ALL rendering related operations requiring any DirectX/OpenGL graphic subsystem interaction (buffer / resource allocation, texture uploads, etc) have to happen on the Unigine main thread, period ! No Unigine flaw, this is more or less a requirement of the DirectX/OpenGL graphic subsystem (even though at least DX11+ in principle allows multi-threaded graphic context preparation, but this is not used in Unigine yet). In consequence this also means that it is highly unsafe to use any Unigine node/object related functionality from a different thread, as implicitely these operations might call DirectX/OpenGL resource operations, causing non-deterministic behaviour (all from success to immediate hard application crash).Design-wise there is also no "but for certain functions it works": if you violate the basis rule, at a certain point in time / under certain conditions / driver versions / application complexity your application will fail, because the base threading-architecture of such an approach is wrong BY PRINCIPLE.BTW the most basic rule regarding multi-threading programming I learned is "don't do it ! don't do it ! don't do it !", simply because the application runtime/debugging complexity increases dramatically for mor complex applications. Ok, sometimes we have to do it, but that we have to do it right and very carefully (summary of above statements)Comming back to your specific use-case some thoughts regarding possible alternative multi-threading approaches:First of all Unigine internally already uses multiple threads for asynchron background resource loading and preparation. These background threads are nicely coordinated with the main rendering thread including per-frame-time management e.g. for uploading resources which otherwise might stall frame rate. Therefore whenever possible (e.g. static resources) try to use Unigine asynchron node loading functionality via WorldLayer objects.Also Unigine FileSystem class allows low-level asynchronous bachground loading of files, images and meshes, which might be helpful. Have a look into the docs.This works fine for static objects, but might not be usable for dynamic objects. The key idea here is to decouple data preparation (your thread) from render object instanciation (Unigine thread). In such a case you could do all kinds of heavy data loading\preparation in your own thread and pass the prepared CPU-side data structures (should be as close as possible to the required render object data feed format) via some queue mechanism to the Unigine main rendering thread for defered Unigine render object creation in the world update() function. It might be nessessary to split larger object creations into some smaller ones spread over some frames.Also it might be helpful (subject to testing) to use some preallocated object pools, to avoid frame rate stalls due to heavy-load GPU resource allocations/deallocations.In any case you have to profile success/failure of employed optimization approaches very carefully, as the results might be quite counter-intuitive. For example we realized that quite often resource creation were quite fast, while resource destruction was one of the actual bottle-necks.I hope some useful general ideas which might be adaptable to your specific use-case.
lin.xianming Posted March 20, 2017 Author Posted March 20, 2017 Hi Ulf, I really appreciate you for your great recommendations. I'm going to try them later on. :)
Recommended Posts