This page has been translated automatically.
视频教程
界面
要领
高级
实用建议
UnigineEditor
界面概述
资产工作流程
设置和首选项
项目开发
调整节点参数
Setting Up Materials
Setting Up Properties
照明
Landscape Tool
Sandworm (Experimental)
使用编辑器工具执行特定任务
Extending Editor Functionality
嵌入式节点类型
Nodes
Objects
Effects
Decals
Light Sources
Geodetics
World Objects
Sound Objects
Pathfinding Objects
Players
编程
基本原理
搭建开发环境
Usage Examples
UnigineScript
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine Tools
GUI
双精度坐标
应用程序接口
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
注意! 这个版本的文档是过时的,因为它描述了一个较老的SDK版本!请切换到最新SDK版本的文档。
注意! 这个版本的文档描述了一个不再受支持的旧SDK版本!请升级到最新的SDK版本。

API中的线程安全

保证在主循环中安全地使用Unigine API对象。 当涉及到多个用户线程时,事情变得更加复杂。

由于并非所有的API类都是线程安全的,因此应考虑API成员的行为类型,以在应用程序中实现安全的多线程处理。

所有类型都需要本文介绍的特殊方法。

也可以看看#

处理多个线程#

线程安全对象#

完全线程安全的对象可以在任何线程中自由使用,无论是主循环还是用户循环。之所以提供此功能,是因为线程同步机制使所有关键操作变得原子化并保护了数据结构,从而消除了竞赛条件等问题。

仅允许一个线程同时访问数据,而为其他线程锁定数据,这就是为什么多个线程可能不得不互相等待直到其任务完成。

注意
来自多个线程的许多数据请求可能会由于同步而导致其他性能损失。

以下API成员被认为是线程安全的:

异步加载节点#

不允许在用户线程中加载节点。为此,建议使用 AsyncQueue

AppWorldLogic.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.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;
}

避免死锁#

互锁的可能性,也称为 deadlock ,前提是被锁定对象的函数执行 callback 函数,然后依次调用以下函数:相同的锁定对象。

注意
为防止死锁,应避免从其回调函数中调用可能锁定的对象。

景观地形的操作#

ObjectLandscapeTerrain包含一组旨在获取风景数据和相交检测的线程安全方法。

AppWorldLogic.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.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;
}

与全球地形相交#

ObjectTerrainGlobal包含一组用于某些特殊情况的线程安全方法。

AppWorldLogic.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.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;
}

主循环相关对象#

Node类和 Node-Related类直接涉及到主循环的线程中。他们没有提供同步机制。

为了从用户线程安全地对这些对象进行操作,应首先暂停主循环以避免干扰。然后,您可以运行所需数量的 jobs 来处理节点。完成所有作业后,继续主循环。

注意
Gpu相关的方法必须仅在主循环中调用。

通过处理与主循环相关的对象的所有Engine线程与Engine::swap()的同步来确保线程安全,其中将延迟删除对象。但是用户线程可以与主线程中的Engine::swap()并行执行,在这种情况下,您Engine::swap()期间不应对与主循环相关的对象(例如节点)执行任何操作

在某些典型情况下,建议使用以下对象:

独立于主循环的对象#

主循环中也没有涉及API成员,它们也没有同步算法。

您可以在任何线程中完全管理此类对象,但是请注意,如果需要将其发送到另一个线程(主循环或用户线程),则必须提供手动同步以确保其数据一致性。

为此,您可以自由决定使用include/UnigineThread.h文件中包含的任何方法和类或其他机制。

以下API成员被认为独立于主循环线程:

有关使用基于简单互斥锁(Mutex)的ScopedLock的手动同步的C ++实现的信息,请参见Thread C++ Sample

与GPU相关的对象#

一些成员方法与Graphics API交互,后者仅在主循环中可用。一旦需要调用与gpu相关的函数,就必须将对象传递到主循环并在其中执行调用。

与渲染相关的 类(例如MeshDynamic)应被视为与gpu相关。

此外, 对象相关 类具有与渲染相关的方法,例如render()和其他的。

注意
请注意,仅应从Render/Viewport::callback函数调用与渲染相关的方法(请参见创建example of creating a Render::callback function).

下面,您将找到dynamic_03默认示例的源代码,该示例演示了如何使用异步执行的Marching cubes算法创建动态网格。

dynamic_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;
}

UnigineScript中的线程#

使用UnigineScript工作流程时,还应记住,不得直接在主循环之外修改与主循环相关的对象。相反,建议为此类对象创建一个 twin ,该对象将被异步修改,然后在flush步骤上与原始对象交换。

注意
非可重入UnigineScript函数不适用于多线程。您将必须为每个线程创建一个单独的函数。为此,您可以使用模板

在下面,您将找到一个UnigineScript示例,该示例关于异步管理多个网格集群。您可以将其复制并粘贴到项目的世界脚本文件中。

cluster_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;
}
最新更新: 2020-11-24
Build: ()