Видеоуроки
Interface
Essentials
Advanced
Полезные советы
Принципы работы
Свойства (properties)
Компонентная Система
Рендер
Физика
Редактор UnigineEditor
Обзор интерфейса
Работа с ассетами
Настройки и предпочтения
Работа с проектами
Настройка параметров узла
Setting Up Materials
Setting Up Properties
Освещение
Landscape Tool
Sandworm (Experimental)
Использование инструментов редактора для конкретных задач
Extending Editor Functionality
Программирование
Настройка среды разработки
Примеры использования
UnigineScript
C++
C#
Унифицированный язык шейдеров UUSL
File Formats
Rebuilding the Engine Tools
GUI
Двойная точность координат
API
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
Работа с контентом
Оптимизация контента
Материалы
Art Samples
Tutorials

Потокобезопасность в API

Unigine API objects are guaranteed to be used safely in the main loop. When it comes to multiple user threads, things get a bit more complicated.Объекты Unigine API гарантированно безопасно используются в основном цикле . Когда дело доходит до нескольких пользовательских потоков, все становится немного сложнее.

As not all of API classes are thread-safe, you should take into consideration the behaviour type of an API member to reach safe multithreading in your application. Поскольку не все классы API являются потокобезопасными, вы должны учитывать тип поведения члена API, чтобы обеспечить безопасную многопоточность в вашем приложении.

All types require special approaches described in this article. Все типы требуют особых подходов, описанных в этой статье.

See AlsoСмотрите также#

Dealing with Multiple ThreadsРабота с несколькими потоками#

Thread-Safe ObjectsПотокобезопасные объекты#

Fully thread-safe objects are free to use in any thread, be it the main loop or a user's one. This is provided due to thread synchronization mechanisms making all critical operations atomic and protecting data structures, it negates issues like race condition and others. Полностью потокобезопасные объекты можно использовать в любом потоке, будь то основной цикл или цикл пользователя. Это обеспечивается за счет того, что механизмы синхронизации потоков делают все критические операции атомарными и защищают структуры данных, они устраняют такие проблемы, как состояние гонки и другие.

Only one thread is allowed to access data at the same time, while data is locked for other threads, that is why multiple threads may have to wait for each other until their tasks are finished. Только одному потоку разрешен доступ к данным одновременно, в то время как данные заблокированы для других потоков, поэтому нескольким потокам, возможно, придется ждать друг друга, пока их задачи не будут завершены.

Примечание
Many requests to data from multiple threads may cause additional performance loss due to synchronization. Множество запросов к данным из нескольких потоков могут вызвать дополнительную потерю производительности из-за синхронизации.

The following API members are considered to be thread-safe: Следующие члены API считаются поточно-ориентированными:

Loading Nodes AsynchronouslyАсинхронная загрузка узлов#

It is not allowed to load nodes in user threads. For that purpose the AsyncQueue Class is recommended to be used. Запрещается загружать узлы в пользовательских потоках. Для этого рекомендуется использовать класс AsyncQueue .

AppWorldLogic.hAppWorldLogic.h

Исходный код (UnigineScript)
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__

#include <UnigineLogic.h>
#include <UnigineStreams.h>
#include <UnigineThread.h>

using namespace Unigine;
using namespace Math;

class AppWorldLogic : public Unigine::WorldLogic {
	
public:
	AppWorldLogic() {}
	virtual ~AppWorldLogic() {}
	
	virtual int init();
	virtual int shutdown();

private:
	Thread *thread1;
	Thread *thread2;
};

#endif // __APP_WORLD_LOGIC_H__

AppWorldLogic.cppAppWorldLogic.cpp

Исходный код (UnigineScript)
#include "AppWorldLogic.h"
#include <UnigineAsyncQueue.h>

class MeshProducerThread : public Thread
{
public:
	MeshProducerThread()
	{
		callback = AsyncQueue::addCallback(AsyncQueue::CALLBACK_MESH_LOADED, MakeCallback(this, &MeshProducerThread::mesh_loaded));
	}
	~MeshProducerThread()
	{
		AsyncQueue::removeCallback(AsyncQueue::CALLBACK_MESH_LOADED, callback);
	}

public:
	void process()
	{
		while (isRunning())
		{
			mesh_id = AsyncQueue::loadMesh("core\\meshes\\material_ball.mesh");
			wait();
			Log::message("Thread %d: mesh loaded\n", getID());
			AsyncQueue::takeMesh(mesh_id);
		}
	}

private:
	void mesh_loaded(const char *name, int id)
	{
		if (mesh_id == id)
			signal();
	}

private:
	int mesh_id = 0;
	int callback = 0;
};

int AppWorldLogic::init()
{
	thread1 = new MeshProducerThread();
	thread1->run();
	thread2 = new MeshProducerThread();
	thread2->run();

	return 1;
}

int AppWorldLogic::shutdown()
{
	thread1->stop();
	thread2->stop();

	return 1;
}

Avoiding DeadlocksКак избежать взаимных блокировок#

There is a possibility of mutual locking, also known as deadlock, on condition that a function of a locked object executes a callback function which in turn calls a function of the same locked object. Существует возможность взаимной блокировки, также известной как deadlock , при условии, что функция заблокированного объекта выполняет функцию обратного вызова (callback), которая, в свою очередь, вызывает функцию тот же заблокированный объект.

Примечание
To prevent deadlocks you should avoid calling potentially locked objects from their callback functions. Чтобы предотвратить взаимоблокировки, вам следует избегать вызова потенциально заблокированных объектов из их функций обратного вызова.

Operations with Landscape TerrainОперации с ландшафтом#

ObjectLandscapeTerrain classes contain a set of thread-safe methods intended for fetching landscape data and intersection detection. Классы ObjectLandscapeTerrain содержат набор поточно-ориентированных методов, предназначенных для получения данных ландшафта и обнаружения пересечений.

AppWorldLogic.hAppWorldLogic.h

Исходный код (C++)
// Copyright (C), UNIGINE. All rights reserved.

#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__

#include "Unigine.h"

using namespace Unigine;
using namespace Math;

class AppWorldLogic : public Unigine::WorldLogic
{

public:
	AppWorldLogic();
	virtual ~AppWorldLogic();

	int init() override;

	int update() override;

	int shutdown() override;

private:
	Unigine::Vector<Thread*> threads;
};

#endif // __APP_WORLD_LOGIC_H__

AppWorldLogic.cppAppWorldLogic.cpp

Исходный код (C++)
#include "AppWorldLogic.h"

class TerrainIntersectionThread : public Thread
{
public:
	TerrainIntersectionThread(PlayerPtr m)
	{
		main_player = m;
	}

	void process() override
	{
		if (!main_player)
			return;
		while (isRunning())
		{

			float x = Game::getRandomFloat(-1000.0f, 1000.0f);
			float y = Game::getRandomFloat(-1000.0f, 1000.0f);

			if (!fetch)
			{
				// create fetch
				fetch = LandscapeFetch::create();

				// set mask
				fetch->setUsesHeight(true);
				fetch->setUsesNormal(true);
				fetch->setUsesAlbedo(true);
				fetch->setUsesMask(0, true);
				fetch->setUsesMask(1, true);
				fetch->setUsesMask(2, true);
				fetch->setUsesMask(3, true);

				fetch->intersectionAsync(vec3{ x, y, 10000.0f }, vec3{ x, y, 0.0 }, false);
			}
			else
			{
				if (fetch->isAsyncCompleted())
				{
					if (fetch->isIntersection())
					{
						Vec3 point = fetch->getPosition();
						Visualizer::renderVector(point, point + Vec3::UP * 10, vec4::BLUE);
						Visualizer::renderVector(point, point + Vec3(fetch->getNormal() * 10), vec4::RED);
						Visualizer::renderSolidSphere(1, translate(point), vec4::BLACK);

						String string;
						string += String::format("Height : %f\n", fetch->getHeight());

						string += "Masks: \n";

						auto terrain = Landscape::getActiveTerrain();
						for (int i = 0; i < 4; i++)
						{
							// getName() is not thread-safe,
							// do not change the mask name in other threads when getting
							string += String::format(" - \"%s\": %.2f\n", terrain->getDetailMask(i)->getName(), fetch->getMask(i));
						}
						Visualizer::renderMessage3D(point, vec3(1, 1, 0), string.get(), vec4::GREEN, 1);
					}
					else
					{
						Visualizer::renderMessage3D(vec3(x, y, 0), vec3(1, 1, 0), "Out of terrain", vec4::RED, 1);
					}

					fetch->intersectionAsync(vec3{ x, y, 10000.0f }, vec3{ x, y, 0.0 }, false);
				}
			}
		}
	}

private:
	LandscapeFetchPtr fetch;
	PlayerPtr main_player;
};

AppWorldLogic::AppWorldLogic()
{
}

AppWorldLogic::~AppWorldLogic()
{
}

int AppWorldLogic::init()
{
	PlayerPtr main_player = checked_ptr_cast<Player>(World::getNodeByName("main_player"));

	int num_thread = 4;
	for (int i = 0; i < num_thread; ++i)
	{
		Thread *thread = new TerrainIntersectionThread(main_player);
		thread->run();
		threads.push_back(thread);
	}
	Visualizer::setEnabled(true);

	return 1;
}

int AppWorldLogic::update()
{

	return 1;
}

int AppWorldLogic::shutdown()
{
	for (Thread *thread : threads)
	{
		thread->stop();
		delete thread;
	}

	return 1;
}

Intersections with Global TerrainПересечения с Global Terrain#

ObjectTerrainGlobal contains a set of thread-safe methods intended for some special use cases. ObjectTerrainGlobal содержит набор потоковобезопасных методов, предназначенных для некоторых особых случаев использования.

AppWorldLogic.hAppWorldLogic.h

Исходный код (C++)
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__

#include <UnigineLogic.h>
#include <UnigineStreams.h>
#include <UnigineThread.h>
#include <UnigineVector.h>

using namespace Unigine;
using namespace Math;

class AppWorldLogic : public Unigine::WorldLogic {

public:
	AppWorldLogic();
	virtual ~AppWorldLogic();
	
	virtual int init();
	virtual int shutdown();

private:
	Unigine::Vector<Thread*> threads;
};

#endif // __APP_WORLD_LOGIC_H__

AppWorldLogic.cppAppWorldLogic.cpp

Исходный код (C++)
#include "AppWorldLogic.h"

#include <UnigineEditor.h>
#include <UnigineObjects.h>
#include <UnigineGame.h>

class TerrainIntersectionThread : public Thread
{
public:
	TerrainIntersectionThread(ObjectTerrainGlobalPtr terrain_)
	{
		terrain = terrain_;
		intersection = ObjectIntersection::create();
	}

	void process() override
	{
		while (isRunning())
		{
			float x = Game::getRandomFloat(-1000.0f, 1000.0f);
			float y = Game::getRandomFloat(-1000.0f, 1000.0f);

			int success = terrain->getIntersection(vec3{ x, y, 10000.0f }, vec3{ x, y, 0.0 }, intersection, 0);
			if (success)
			{
				const auto intersection_point = intersection->getPoint();
				Log::message("Thread %d: %f %f %f\n", getID(), intersection_point.x, intersection_point.y, intersection_point.z);
			}
		}
	}

private:
	ObjectTerrainGlobalPtr terrain;
	ObjectIntersectionPtr intersection;
};

AppWorldLogic::AppWorldLogic()
{
}

AppWorldLogic::~AppWorldLogic()
{
}

int AppWorldLogic::init()
{
	const auto terrain = checked_ptr_cast<ObjectTerrainGlobal>(World::getNodeByName("Landscape"));

	int num_thread = 4;
	for (int i = 0; i < num_thread; ++i)
	{
		Thread *thread = new TerrainIntersectionThread(terrain);
		thread->run();
		threads.push_back(thread);
	}

	return 1;
}

int AppWorldLogic::shutdown()
{
	for (Thread *thread : threads)
	{
		thread->stop();
		delete thread;
	}

	return 1;
}

Main-Loop-Dependent ObjectsОбъекты, зависящие от основного цикла#

The Node class and the Node-Related classes are directly involved into threads of the main loop. They don't have synchronization mechanisms provided. Класс Node и классы, связанные с узлом , напрямую участвуют в потоках основного цикла. У них нет механизмов синхронизации.

To safely operate on these objects from user threads, you should firstly pause the main loop to avoid interference. Then, you can run a required number of jobs for processing of nodes. After all jobs are done, continue the main loop. Чтобы безопасно работать с этими объектами из пользовательских потоков, вы должны сначала приостановить основной цикл, чтобы избежать помех. Затем вы можете запустить необходимое количество заданий для обработки узлов. После того, как все работы будут выполнены, продолжите основной цикл.

Примечание
Gpu-related methods must be called only in the main loop. Методы, связанные с графическим процессором , должны вызываться только в основном цикле.

Thread safety is ensured by synchronization of all Engine's threads dealing with main-loop-dependent objects with Engine::swap(), where delayed deletion of objects is performed. But user threads can be executed in parallel with Engine::swap() in the main thread, in such cases you shouldn't perform any manipulations with main-loop dependent objects (such as nodes) during Engine::swap(). Безопасность потоков обеспечивается синхронизацией всех потоков Engine, работающих с объектами, зависящими от основного цикла, с Engine::swap(), где выполняется отложенное удаление объектов. Но пользовательские потоки могут выполняться параллельно с Engine::swap() в основном потоке, в таких случаях вам не следует выполнять какие-либо манипуляции с объектами, зависящими от основного цикла (такими как узлы) во время Engine::swap() .

For some typical cases it is recommended to use the following objects: Для некоторых типичных случаев рекомендуется использовать следующие объекты:

  • AsyncQueue Class is useful for asynchronous loading of resources. Класс AsyncQueue предназначен для асинхронной загрузки ресурсов.
  • Async Class is intended for running custom tasks in asynchronous way. Async Class предназначен для асинхронного выполнения пользовательских задач.

Main-Loop-Independent ObjectsОбъекты, не зависящие от основного цикла#

There are also API members which are not involved into the main loop, they don't have synchronization algorithms as well. Есть также члены API, которые не задействованы в основном цикле, у них также нет алгоритмов синхронизации.

You can fully manage such an object in any thread, but please note that if you need to send it to another thread, either the main loop or a user's thread, you have to provide manual synchronization for its data consistency. Вы можете полностью управлять таким объектом в любом потоке, но обратите внимание, что если вам нужно отправить его в другой поток, либо в основной цикл, либо в поток пользователя, вы должны обеспечить ручную синхронизацию для его согласованности данных.

For this purpose you are free to use any methods and classes contained in the include/UnigineThread.h file or other mechanisms at your discretion. Для этой цели вы можете использовать любые методы и классы, содержащиеся в файле include/UnigineThread.h, или другие механизмы по своему усмотрению.

The following API members are considered to be independent of the main loop threads: Следующие члены API считаются независимыми от потоков основного цикла:

See the Thread C++ Sample for C++ implementation of manual synchronization using the ScopedLock based on the simple mutex (Mutex). См. Thread C++ Sample для реализации C ++ ручной синхронизации с использованием ScopedLock на основе простого мьютекса (Mutex).

GPU-Related ObjectsОбъекты, связанные с GPU#

Some members methods interact with Graphics API, which is available only in the main loop. Once you need to call a gpu-related function, you have to pass the object to the main loop and perform calling in it. Некоторые методы-члены взаимодействуют с Graphics API, который доступен только в основном цикле. Как только вам нужно вызвать функцию, связанную с графическим процессором, вы должны передать объект в основной цикл и выполнить в нем вызов.

The Rendering-Related Classes (e.g. MeshDynamic) should be considered as gpu-related.Классы, связанные с рендерингом (например, MeshDynamic), следует рассматривать как связанные с графическим процессором.

Also, the Object-Related Classes have rendering-related methods, such as render() and other ones.Кроме того, классы, относящиеся к объектам, имеют методы, связанные с отрисовкой, такие как render() и другие.

Примечание
Note that rendering-related methods should be called only from Render/Viewport::callback functions (see an example of creating a Render::callback function). Обратите внимание, что методы, связанные с отрисовкой, должны вызываться только из функций Render/Viewport::callback (см. пример создания функции Render::callback).

Below you'll find the source code of the dynamic_03 default sample which demonstrates how to create a dynamic mesh by using the Marching cubes algorithm performed asynchronously. Ниже вы найдете исходный код примера dynamic_03, который демонстрирует, как создать динамическую сетку с использованием алгоритма Marching Cubes, выполняемого асинхронно.

dynamic_03.uscdynamic_03.usc

Исходный код (UnigineScript)
#include <core/scripts/samples.h>
#include <samples/objects/dynamic_01.h>

/*
 */
Async async_0;
Async async_1;
int size = 32;
float field_0[size * size * size];
float field_1[size * size * size];
int flags_0[size * size * size];
int flags_1[size * size * size];
ObjectMeshDynamic mesh_0;
ObjectMeshDynamic mesh_1;

using Unigine::Samples;

/*
 */
string mesh_material_names[] = ( "objects_mesh_red", "objects_mesh_green", "objects_mesh_blue", "objects_mesh_orange", "objects_mesh_yellow" );

string get_mesh_material(int material) {
	return mesh_material_names[abs(material) % mesh_material_names.size()];
}

/*
 */
void update_thread() {
	
	while(1) {
		
		float time = engine.game.getTime();
		
		// wait async
		if(async_1 == NULL) async_1 = new Async();
		while(async_1 != NULL && async_1.isRunning()) wait;
		if(async_1 == NULL) continue;
		async_1.clearResult();
		
		// copy mesh
		Mesh mesh = new Mesh();
		mesh_1.getMesh(mesh);
		mesh_0.setMesh(mesh);
		mesh_0.setMaterial(get_mesh_material(1),"*");
		delete mesh;
		
		// wait async
		if(async_0 == NULL) async_0 = new Async();
		while(async_0 != NULL && async_0.isRunning()) wait;
		if(async_0 == NULL) continue;
		async_0.clearResult();
		
		// swap buffers
		field_1.swap(field_0);
		flags_1.swap(flags_0);
		
		// create field
		float angle = sin(time) + 3.0f;
		mat4 transform = rotateZ(time * 25.0f) * scale(vec3(5.0f / size)) * translate(vec3(-size / 2.0f));
		async_0.run(functionid(create_field),field_0.id(),flags_0.id(),size,transform,angle);
		
		// create mesh
		async_1.run(functionid(marching_cubes),mesh_1,field_1.id(),flags_1.id(),size);
		
		wait;
	}
}

/*
 */
int init() {
	
	createInterface("samples/objects/dynamic_03.world");
	engine.render.loadSettings(fullPath("samples/common/world/render.render"));
	createDefaultPlayer(Vec3(30.0f,0.0f,20.0f));
	createDefaultPlane();
	
	mesh_0 = addToEditor(new ObjectMeshDynamic(OBJECT_DYNAMIC_ALL));
	mesh_0.setWorldTransform(Mat4(scale(vec3(16.0f / size)) * translate(-size / 2.0f,-size / 2.0f,0.0f)));
	
	mesh_1 = new ObjectMeshDynamic(1);
	mesh_1.setEnabled(0);
	
	setDescription(format("Async dynamic marching cubes on %dx%dx%d grid",size,size,size));
	
	thread("update_thread");
	
	return 1;
}

/*
 */
void shutdown() {
	
	if(async_0 != NULL) async_0.wait();
	if(async_1 != NULL) async_1.wait();
	return 1;
}

Threads in UnigineScriptПотоки в UnigineScript#

When using UnigineScript workflow, you should also keep in mind that main-loop-dependent objects must not be directly modified out of the main loop. Instead, it is suggested to create a twin for such an object which will be modified asynchronously and then swapped with the original object on the flush step. При использовании рабочего процесса UnigineScript вы также должны помнить, что объекты, зависящие от основного цикла, не должны изменяться напрямую вне основного цикла. Вместо этого предлагается создать двойника для такого объекта, который будет асинхронно изменен, а затем заменен на исходный объект на шаге flush.

Примечание
Non-reentrant UnigineScript functions are not suitable for multithreaded usage. You will have to create a separate function for each thread. For that you can use Templates. Функции UnigineScript без повторного входа не подходят для многопоточного использования. Вам нужно будет создать отдельную функцию для каждого потока. Для этого вы можете использовать Шаблоны .

Below you'll find a UnigineScript sample on managing several mesh clusters asynchronously. You can copy and paste it to the world script file of your project. Ниже вы найдете пример UnigineScript по асинхронному управлению несколькими кластерами сетки. Вы можете скопировать и вставить его в файл сценария мира вашего проекта.

cluster_03.usccluster_03.usc

Исходный код (UnigineScript)
#include <core/unigine.h>
#include <core/scripts/samples.h>

using Unigine::Samples;

#define NUM_CLUSTERS 4
int size = 60;

// a class for asynchronous mesh cluster
class AsyncCluster
{
	public:
	Mat4 transforms[0];
	// original mesh cluster
	ObjectMeshCluster cluster;
	// a twin for async modification
	ObjectMeshCluster cluster_async;
	Async async;
};
AsyncCluster clusters[NUM_CLUSTERS];

string mesh_material_names[] = ( "stress_mesh_red", "stress_mesh_green", "stress_mesh_blue", "stress_mesh_orange", "stress_mesh_yellow" );

string get_mesh_material(int material) {
	return mesh_material_names[abs(material) % mesh_material_names.size()];
}

// a template to generate a function transforming a cluster in each thread
template async_transforms<NUM, OFFSET_X, OFFSET_Y> void async_transforms_ ## NUM(ObjectMeshCluster cluster_async, float transforms[], float time, int size) {
	
	Vec3 offset = Vec3(OFFSET_X - 0.5f, OFFSET_Y - 0.5f, 0.0f) * (size + 0.5f) * 2;
	
	int num = 0;
	for(int y = -size; y <= size; y++) {
		for(int x = -size; x <= size; x++) {
			float rand = sin(frac(num * 0.333f) + x * y * (NUM + 1));
			
			Vec3 pos = (Vec3(x, y, sin(time * rand * 2.0f) + 1.5f) + offset) * 2.0f;
			transforms[num] = translate(pos) * rotateZ(time * 25 * rand);
			num++;
		}
	}
	
	cluster_async.createMeshes(transforms);
}

async_transforms<0,0,0>;
async_transforms<1,0,1>;
async_transforms<2,1,0>;
async_transforms<3,1,1>;

void update_thread() {
	
	while(1) {
		
		// wait async
		for(int i = 0; i < NUM_CLUSTERS; i++) {
			while(clusters[i].async.isRunning())
				wait;
		}
		
		for(int i = 0; i < NUM_CLUSTERS; i++) {
			AsyncCluster c = clusters[i];
			
			c.async.clearResult();
			c.cluster.swap(c.cluster_async);
			c.cluster.setEnabled(1);
			c.cluster_async.setEnabled(0);
			c.async.run("async_transforms_" + i, c.cluster_async, c.transforms.id(), engine.game.getTime(), size);
		}
		
		wait;
	}
}

int init() {
	// create scene
	PlayerSpectator player = new PlayerSpectator();
	player.setPosition(Vec3(30.0f,0.0f,20.0f));
	player.setDirection(vec3(-1.0f, 0.0f, -0.5f));
	engine.game.setPlayer(player);
	
	for(int i = 0; i < NUM_CLUSTERS; i++) {
		AsyncCluster c = new AsyncCluster();
		c.cluster = new ObjectMeshCluster(fullPath("samples/common/meshes/box.mesh"));
		c.cluster.setMaterial(get_mesh_material(i),"*");
		c.cluster_async = class_append(node_cast(c.cluster.clone()));
		c.async = new Async();
		int num = pow(size * 2 + 1, 2);
		c.transforms.resize(num);
		clusters[i] = c;
	}
	
	thread("update_thread");
	
	int num = pow(size * 2 + 1, 2) * NUM_CLUSTERS;
	log.message("ObjectMeshCluster with %d dynamic instances",num);
	
	return 1;
}

/*
 */
void shutdown() {
	
	for(int i = 0; i < NUM_CLUSTERS; i++) {
		clusters[i].async.wait();
	}
	
	return 1;
}
Последнее обновление: 10.10.2020