This page has been translated automatically.
视频教程
界面
要领
高级
实用建议
专业(SIM)
UnigineEditor
界面概述
资源工作流程
版本控制
设置和首选项
项目开发
调整节点参数
Setting Up Materials
设置属性
照明
Sandworm
使用编辑器工具执行特定任务
嵌入式节点类型
Nodes
Objects
Effects
Decals
光源
Geodetics
World Nodes
Sound Objects
Pathfinding Objects
Players
编程
基本原理
搭建开发环境
使用范例
C++
C#
UnigineScript
UUSL (Unified UNIGINE Shader Language)
Plugins
File Formats
材质和着色器
Rebuilding the Engine Tools
GUI
双精度坐标
应用程序接口
Animations-Related Classes
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
VR-Related Classes
创建内容
内容优化
材质
Material Nodes Library
Miscellaneous
Input
Math
Matrix
Textures
Art Samples
Tutorials

在编辑器插件中管理资源

This article contains information on working with assets in user plugins for UnigineEditor. Here you will find a brief description of the basic concepts and terms of the Asset System and learn how to manage resources (assets) in your custom tools based on UnigineEditor functionality.本文包含关于如何在UnigineEditor的用户插件中使用资源的信息。在这里,您将找到资源系统的基本概念和术语的简要描述,并学习如何在基于UnigineEditor功能的自定义工具中管理资源(资源)。

See Also另请参阅#

Basic Concepts基本概念#

Asset is the "unit of work" - any item that you can use within a project. Some assets are called native (i.e. can be used by the Engine "as is"), while others (non-native) require to be converted. The result of conversion is called a runtime file (or simply runtime). Asset是“工作单元”——你可以在项目中使用的任何项目。有些资源被称为native(即可以被Engine“as is”使用),而其他资源(非native)需要被转换。转换的结果称为运行时文件(或简单地称为运行时)。

For a single asset one or multiple runtimes can be generated, one of the runtimes is called primary (uniquely assoсiated with the asset, acting like an implied reference), while others are considered auxiliary (e.g., a model.fbx-asset produces multiple runtimes - .node, .mesh, .texture, .mat runtimes, but only the model.node is considered primary). When there is only one runtime produced it is considered as primary.对于单个资源,可以生成一个或多个运行时,其中一个运行时称为primary(与资源惟一关联,充当隐含引用),而其他运行时被视为辅助的(例如,model.fbx-资源生成多个运行时- .node, .mesh, .texture, .mat运行时,但只有model.node被视为主要的)。当只产生一个运行时时,它被认为是 primary

A metafile (.meta) is generated for each asset, contains important information about it: version, parameters, hash, runtime information, and a GUID (globally unique identifier) identifying its location in the project. GUIDs are used to refer to all assets and runtimes in the project instead of paths, giving you a lot of flexibility in managing resources, as you can rename or move assets and be sure that all links between them are kept.为每个资源生成元文件 (.meta),其中包含关于资源的重要信息:版本、参数、散列、运行时信息,以及标识其在项目中的位置的GUID (globally unique identifier)。guid用于引用项目中的所有资源和运行时,而不是路径,这为管理资源提供了很大的灵活性,因为您可以重命名或移动资源,并确保保留它们之间的所有链接。

Normally all your project's resources (files, metafiles, runtime files) are stored in the project's data folder, but UNIGINE's virtual File System enables you to add links to external storage locations or packages via mount points. A mount point can be created via the Asset Browser (Create Mount Point) or via API. It is represented as a .umount file in the JSON format that stores a reference to an external directory or package as an absolute path or a path relative to the current directory. Also .umount file stores version of UNIGINE SDK in which the mount has been created, along with access information. All folders inside the mount point are treated by the File System as usual folders with assets, runtimes, and metafiles inside the data directory.通常你所有的项目资源(文件,元文件,运行时文件)都存储在项目的data文件夹中,但是UNIGINE的虚拟文件系统允许你通过挂载点添加到外部存储位置或包的链接。可以通过Asset Browser (Create Mount Point)或API创建挂载点。它被表示为JSON格式的.umount文件,该文件将对外部目录或包的引用存储为绝对路径或相对于当前目录的路径。.umount文件还存储了创建挂载的UNIGINE SDK版本以及访问信息。文件系统将挂载点中的所有文件夹视为普通文件夹,其资源、运行时和元文件位于data目录中。

Asset Manager资源管理公司#

Assets in UnigineEditor are managed... yes, by Asset Manager. It is a class that extends the functionality of the UNIGINE's File System. It enables you to check availability of a resource with a specific GUID, get its path, subscribe for the signals to perform certain actions when an asset is added, moved, updated, or deleted. AssetManager allows you to track assets changes in real time. You can modify your assets at any time after importing, the Asset System will notice when you save new changes to the file and will re-import it as necessary. In some cases it might be necessary to temporary change this behavior and block auto-refreshing (e.g. if you are building a custom integration with a version control system). You can do it using blockAutoRefresh() and unblockAutoRefresh() functions.UnigineEditor中的资源被管理…是的,由Asset Manager。它是一个扩展了UNIGINE的File System功能的类。它使您能够检查具有特定GUID的资源的可用性、获取其路径、订阅在添加、移动、更新或删除资源时执行某些操作的信号。AssetManager允许您实时跟踪资源的变化。您可以在导入后的任何时间修改您的资源,资源系统会注意到您在文件中保存新的更改,并在必要时重新导入它。在某些情况下,可能需要临时更改此行为并阻止自动刷新(例如,如果您正在构建与版本控制系统的自定义集成)。可以使用blockAutoRefresh()unblockAutoRefresh()函数来实现。

AssetManager enables you to work with:AssetManager使您能够使用:

  • assets and their callbacks资源和回调
  • directories and their callbacks目录和它们的回调
  • mount points挂载点
  • file system watchers文件系统观察人士
  • process callbacks处理回调
  • utils跑龙套

The UnigineEditor API for AssetManager is presented here.提出了AssetManager here UnigineEditor API。

AssetsPluginAssetsPlugin#

Most likely your custom tools that you develop on the basis of UnigineEditor's functionality will somehow manage assets. For your convenience there is a sample plugin demonstrating implementation of basic asset management operations using the AssetManager class. It is called AssetsPlugin and can be used as a template for your tool.最有可能的定制工具,你开发的基础上UnigineEditor会管理资源的功能。为了使您有一个示例插件展示实现的基本使用AssetManager类资源管理业务。它被称为AssetsPlugin和工具可以用来作为模板。

Assets Plugin

Assets PluginAssets插件

Implemented Functions 实现功能

This plugin template implements the basic functionality of the Asset Manager. The plugin interacts with the Asset Manager system and adds a widget to Window Manager. AssetsPlugin allows you to:这个插件模板实现Asset Manager的基本功能。插件与Asset Manager交互系统和部件Window Manager补充道。AssetsPlugin允许你:

Import New Assets Synchronously同步导入新资源#

The type of operation is used when imported asset(s) is critically important for the current execution of the application. The importing process is executed in the main thread of the application. It represents a blocking system call and the function will not return until the operation is complete.当导入的资源对应用程序的当前执行非常重要时,使用操作的类型。导入过程在应用程序的主线程中执行。它表示一个阻塞系统调用,该函数在操作完成之前不会返回。

This operation is implemented in the plugin as import_new_asset_synchronously() function.这个操作是在插件实现import_new_asset_synchronously()函数。

AssetsPlugin.cpp

源代码 (C++)
// Method importing a new asset and waiting for the process to complete (blocking the thread)
void AssetsPlugin::import_new_asset_synchronously()
{
	// calling an auxiliary function to create a new asset and import it, displaying information on imported asset on success, or an error notification
	String result = create_and_import_new_asset_synchronously();
	if (!result.empty())
	{
		success_notification(
			String::format("Asset has imported successfully: \"%s\"", result.get()));

		// set current Editor selection to the imported asset and print information about it
		select_asset(result);

		print_asset_information(result);
	}
	else
	{
		error_notification(String::format("Can't import asset: \"%s\"", result.get()));
	}
}

// Method creating and importing an asset synchronously (blocking the thread until the operation is completed)
String AssetsPlugin::create_and_import_new_asset_synchronously() const
{
	String result;

	// making a base path for a new asset to be imported
	const StringStack<> &base_asset_path = String::joinPaths(DIRECTORY_FOR_NEW_ASSETS,
		String::basename(TEXTURE_PATH));

	// asking  the Asset Manager to generate a unique 
	String new_texture_path = AssetManager::generateUniquePath(base_asset_path);
	if (!create_asset(TEXTURE_PATH, new_texture_path))
	{
		return result;
	}
	// waiting for the texture asset to be imported by the AssetManager and returning the result (new texture path on success of an empty string on failure)
	if (AssetManager::importAssetSync(new_texture_path))
	{
		result = std::move(new_texture_path);
	}

	return result;
}

Import New Assets Asynchronously异步导入新资源#

This type of operation is used when imported asset(s) is not critically important for the current execution of the application and can be performed in the background of the working process (e.g. adding a library of textures or models to be used later by the user). It represents a non-blocking call, the function returns immediately as soon as the operation is put to a queue.这种类型的操作时使用进口资源(s)为当前执行的不是至关重要的应用程序,可以在后台执行的工作流程(例如添加纹理库或模型后由用户使用)。它代表了一个非阻塞调用,函数立即返回尽快操作队列。

注意
There is only a notification about the start of the process, no information about its status or any errors occurred. To catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method.只有一个关于进程启动的通知,没有关于其状态或发生任何错误的信息。为了捕获操作的完成,通过AssetManager::addProcessEndCallback()方法设置回调。

This operation is implemented in the plugin as import_new_asset_asynchronously() function.这个操作是在插件实现import_new_asset_asynchronously()函数。

AssetsPlugin.cpp

源代码 (C++)
// Method importing an asset in the background (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::import_new_asset_asynchronously()
{
	// making a base target path for the asset and generating a unique virtual path for it
	const StringStack<> &base_asset_path = String::joinPaths(DIRECTORY_FOR_NEW_ASSETS,
		String::basename(TEXTURE_PATH));
	const String &new_texture_path = AssetManager::generateUniquePath(base_asset_path);
	if (!create_asset(TEXTURE_PATH, new_texture_path))
	{
		return;
	}

	// launching the importing process in the background (asynchronously, without blocking the main thread) and displaying a notification, if it has started successfully
	// to catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method (see method "process_end()" here)
	if (AssetManager::importAssetAsync(new_texture_path))
	{
		success_notification(String::format("Asset import process has started successfully: \"%s\"",
			new_texture_path.get()));
	}
	else
	{
		error_notification(
			String::format("Can't start asset import process: \"%s\"", new_texture_path.get()));
	}
}

Remove Assets Asynchronously异步删除资源#

This type of operation is used when removing of asset(s) is not critically important for the current execution of the application and can be performed in the background of the working process (e.g. removing a pack of unused textures). It represents a non-blocking call, the function returns immediately as soon as the operation is put to a queue.这种类型的操作是删除资源时使用(s)不是至关重要的当前执行的应用程序,可以在后台执行的工作流程(例如删除一包未使用的纹理)。它代表了一个非阻塞调用,函数立即返回尽快操作队列。

注意
Removing the asset from a project means completely removing it from all of the project's elements referring to it. There is only a notification about the start of the process, no information about its status or any errors occurred. To catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method.从项目中删除资源意味着将其从引用它的所有项目元素中完全删除。只有一个关于进程启动的通知,没有关于其状态或发生任何错误的信息。为了捕获操作的完成,通过AssetManager::addProcessEndCallback()方法设置回调。

This operation is implemented in the plugin as remove_asset_asynchronously() function.这个操作是在插件实现remove_asset_asynchronously()函数。

AssetsPlugin.cpp

源代码 (C++)
// Method removing an asset in the background (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::remove_asset_asynchronously()
{
	// checking whether there are any assets in the source directory to remove (if not, report and return)
	const Vector<String> &asset_paths = AssetManager::getAssetPathsForDirectory(
		DIRECTORY_FOR_NEW_ASSETS);
	if (asset_paths.empty())
	{
		error_notification(String::format("There are no assets to remove: \"%s\"\n",
			DIRECTORY_FOR_NEW_ASSETS));
		return;
	}

	// launching the asset removal process in the background (asynchronously, without blocking the main thread) and displaying a notification, if it has started successfully
	// to catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method (see method "process_end()" here)
	const String &asset_path_to_remove = asset_paths.last();
	if (AssetManager::removeAssetAsync(asset_path_to_remove))
	{
		success_notification(String::format("Asset removal process has started successfully: \"%s\"",
			asset_path_to_remove.get()));
	}
	else
	{
		error_notification(
			String::format("Can't start asset removal process: \"%s\"", asset_path_to_remove.get()));
	}
}

Move Assets Asynchronously异步转移资源#

This type of operation is used when moving asset(s) is not critically important for the current execution of the application and can be performed in the background of the working process. It represents a non-blocking call, the function returns immediately as soon as the operation is put to a queue. It is used to manage files. All virtual paths of assets are kept protected.这种类型的操作时使用移动资源(s)不是至关重要的当前执行的应用程序,可以在后台执行的工作过程。它代表了一个非阻塞调用,函数立即返回尽快操作队列。它是用于管理文件。所有的虚拟路径的资源保护。

注意
There is only a notification about the start of the process, no information about its status or any errors occurred. To catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method.只有一个通知关于过程的开始,没有地位的信息或任何错误发生。捕捉完成操作设置回调通过AssetManager::addProcessEndCallback()方法。

This operation is implemented in the plugin as move_asset_asynchronously() function.该操作在插件中作为move_asset_asynchronously()函数实现。

AssetsPlugin.cpp

源代码 (C++)
// Method moving an asset to a new location in the background (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::move_asset_asynchronously()
{
	// checking whether there are any assets in the source directory to move (if not, report and return)
	const Vector<String> &asset_paths = AssetManager::getAssetPathsForDirectory(
		DIRECTORY_FOR_NEW_ASSETS);
	if (asset_paths.empty())
	{
		error_notification(String::format("There are no assets to move: \"%s\"\n",
			DIRECTORY_FOR_NEW_ASSETS));
		return;
	}

	// trying to create a destination directory and displaying error notification in case of failure
	if (!AssetManager::createDirectory(DIRECTORY_FOR_MOVED_ASSETS))
	{
		error_notification(
			String::format("Can't create directory: \"%s\"", DIRECTORY_FOR_MOVED_ASSETS));
		return;
	}

	// making a new asset path and generating a unique virtual path for it
	const String &asset_path_to_move = asset_paths.last();
	const StringStack<> &base_asset_path = String::joinPaths(DIRECTORY_FOR_MOVED_ASSETS,
		asset_path_to_move.basename());
	const String &new_asset_path = AssetManager::generateUniquePath(base_asset_path);

	// launching the "move asset" process in the background (asynchronously, without blocking the main thread) and displaying a notification, if it has started successfully
	// to catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method (see method "process_end()" here)
	if (AssetManager::moveAssetAsync(asset_path_to_move, new_asset_path))
	{
		success_notification(
			String::format("Asset moving process has started successfully: from \"%s\" to \"%s\"",
				asset_path_to_move.get(), new_asset_path.get()));
	}
	else
	{
		error_notification(String::format("Can't start asset moving process: from \"%s\" to \"%s\"",
			asset_path_to_move.get(), new_asset_path.get()));
	}
}

Rename Assets Asynchronously重命名资源异步#

This type of operation is used when renaming asset(s) is not critically important for the current execution of the application and can be performed in the background of the working process (e.g. adding a prefix for a set of textures in the library). It represents a non-blocking call, the function returns immediately as soon as the operation is put to a queue. It is used to manage files. All virtual paths of assets are kept protected.当重命名资源对应用程序的当前执行不是很重要并且可以在工作进程的后台执行时使用这种类型的操作(例如,为库中的一组纹理添加前缀)。 它代表一个非阻塞调用,只要将操作放入队列,函数就会立即返回。 它用于管理文件。 资源的所有虚拟路径都受到保护。

注意
There is only a notification about the start of the process, no information about its status or any errors occurred. To catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method.只有一个关于进程启动的通知,没有关于其状态或发生任何错误的信息。为了捕获操作的完成,通过AssetManager::addProcessEndCallback()方法设置回调。

This operation is implemented in the plugin as rename_asset_asynchronously() function.这个操作是在插件实现rename_asset_asynchronously()函数。

AssetsPlugin.cpp

源代码 (C++)
// Method renaming an asset asynchronously (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::rename_asset_asynchronously()
{
	// getting paths for all assets in the directory and checking whether it is empty (if so, report and return)
	const Vector<String> &asset_paths = AssetManager::getAssetPathsForDirectory(
		DIRECTORY_FOR_NEW_ASSETS);
	if (asset_paths.empty())
	{
		error_notification(String::format("There are no assets to rename: \"%s\"\n",
			DIRECTORY_FOR_NEW_ASSETS));
		return;
	}

	// as an example, we rename the last file
	const String &asset_path_to_rename = asset_paths.last();

	const StringStack<> &new_asset_dir_path = String::dirname(asset_path_to_rename);
	const char *NEW_ASSET_FILE_NAME = "Renamed Asset";

	// making a new asset path and generating a unique virtual path for it
	StringStack<> new_asset_path = String::joinPaths(new_asset_dir_path, NEW_ASSET_FILE_NAME);
	new_asset_path += '.' + String::extension(asset_path_to_rename);

	new_asset_path = AssetManager::generateUniquePath(new_asset_path);

	// getting a base name for it to set while renaming
	const StringStack<> &new_asset_name = new_asset_path.basename();

	// putting a new "asset rename" task to a queue and displaying the corresponding notification depending on the result
	if (AssetManager::renameAssetAsync(asset_path_to_rename, new_asset_name))
	{
		success_notification(
			String::format("Asset renaming process has started successfully: from \"%s\" to \"%s\"",
				asset_path_to_rename.get(), new_asset_path.get()));
	}
	else
	{
		error_notification(String::format("Can't start asset renaming process: from \"%s\" to \"%s\"",
			asset_path_to_rename.get(), new_asset_path.get()));
	}
}

Copy Assets Asynchronously异步复制资源#

This type of operation is used when copying asset(s) is not critically important for the current execution of the application and can be performed in the background of the working process. It represents a non-blocking call, the function returns immediately as soon as the operation is put to a queue. Can be used, for example, if the same asset with different dimensional parameters is required or to perform some tests with a given world file.当复制资源对应用程序的当前执行不是至关重要的并且可以在工作流程的后台执行时,使用这种类型的操作。 它代表一个非阻塞调用,只要将操作放入队列,函数就会立即返回。 例如,如果需要具有不同尺寸参数的相同资源或使用给定的世界文件执行某些测试,则可以使用。

注意
There is only a notification about the start of the process, no information about its status or any errors occurred. To catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method.只有一个关于进程启动的通知,没有关于其状态或发生任何错误的信息。为了捕获操作的完成,通过AssetManager::addProcessEndCallback()方法设置回调。

This operation is implemented in the plugin as copy_asset_asynchronously() function.该操作在插件中作为copy_asset_asynchronously()函数实现。

AssetsPlugin.cpp

源代码 (C++)
// Method copying an asset asynchronously (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::copy_asset_asynchronously()
{
	// getting paths for all assets in the directory and checking whether it is empty (if so, report and return)
	const Vector<String> &asset_paths = AssetManager::getAssetPathsForDirectory(
		DIRECTORY_FOR_NEW_ASSETS);
	if (asset_paths.empty())
	{
		error_notification(String::format("There are no assets to copy: \"%s\"\n",
			DIRECTORY_FOR_NEW_ASSETS));
		return;
	}

	// as an example, we copy the last file
	const String &asset_path_to_copy = asset_paths.last();

	// creating a new directory
	if (!AssetManager::createDirectory(DIRECTORY_FOR_COPIED_ASSETS))
	{
		error_notification(
			String::format("Can't create directory: \"%s\"", DIRECTORY_FOR_COPIED_ASSETS));
		return;
	}

	// making a base path and generating a unique virtual path for a new copy of the asset
	const StringStack<> &base_asset_path = String::joinPaths(DIRECTORY_FOR_COPIED_ASSETS,
		asset_path_to_copy.basename());
	const String &new_asset_path = AssetManager::generateUniquePath(base_asset_path);

	// putting a new "asset copy" task to a queue and displaying the corresponding notification depending on the result
	if (AssetManager::copyAssetAsync(asset_path_to_copy, new_asset_path))
	{
		success_notification(
			String::format("Asset copying process has started successfully: from \"%s\" to \"%s\"",
				asset_path_to_copy.get(), new_asset_path.get()));
	}
	else
	{
		error_notification(String::format("Can't start asset copying process: from \"%s\" to \"%s\"",
			asset_path_to_copy.get(), new_asset_path.get()));
	}
}

Print Paths and GUIDs of All Known Assets打印所有已知的资源的路径和guid#

Printing out the list of paths and GUIDs for all assets in the root project directory (including all subdirectories). This can be used to iterate over the assets in your project providing information on assets location.打印出所有资源的路径和guid列表在项目根目录(包括所有子目录)。这可以用来遍历资源项目中提供的信息的位置。

This operation is implemented in the plugin as print_all_known_assets() function.该操作在插件中作为print_all_known_assets()函数实现。

AssetsPlugin.cpp

源代码 (C++)
// Method printing out the list of paths and GUIDs for all assets in the root project directory (including all subdirectories)
void AssetsPlugin::print_all_known_assets()
{
	Log::message("[Assets Plugin] All asset paths:\n");
	for (const String &path : AssetManager::getAssetPaths())
	{
		Log::message("\t%s\n", path.get());
	}
	Log::message("\n[Assets Plugin] All asset GUIDs:\n");
	for (const UGUID &guid : AssetManager::getAssetGUIDs())
	{
		Log::message("\t%s\n", guid.getFileSystemString());
	}
	Log::message("\n");
}

Print Full Information About The Asset打印关于资源的全部信息#

Printing out general information for the specified asset path including virtual path, file GUID, access mode, and information about runtimes generated for this asset (GUID, type, alias, and file GUID). You may need this information, for example, when building a dependency graph for your assets.打印指定资源路径的一般信息,包括虚拟路径、文件GUID、访问模式,以及关于为该资源生成的运行时的信息(GUID、类型、别名和文件GUID)。例如,在为资源构建依赖关系图时,您可能需要这些信息。

This operation is implemented in the plugin as print_asset_information() function.这个操作是在插件实现print_asset_information()函数。

AssetsPlugin.cpp

源代码 (C++)
// Method printing out general information for the specified asset path
void AssetsPlugin::print_asset_information(const char *asset_path)
{
	const UGUID &asset_guid = AssetManager::getAssetGUIDFromPath(asset_path);
	// It's only to demonstrate the usage of some API functions:
	Log::message("[Assets Plugin] Asset information:\n");
	Log::message("\tVirtual path: %s\n", AssetManager::getAssetPathFromGUID(asset_guid).get());
	Log::message("\tFile GUID: %s\n", asset_guid.getFileSystemString());
	Log::message("\tAccess: %s\n",
		AssetManager::isAssetWritable(asset_path) ? "read & write" : "read-only");
	auto get_import_parameters_as_string = [asset_path] {
		const CollectionPtr &parameters = AssetManager::getAssetImportParameters(asset_path);
		return convert_vector_to_flat_string(parameters->getNames());
	};
	Log::message("\tImport parameter names: %s\n", get_import_parameters_as_string().get());
 
	// getting the list of GUIDs for all runtimes generated for the asset path and displaying general information about them (if any)
	const Vector<UGUID> &runtime_guids = AssetManager::getRuntimeGUIDs(asset_path);
	if (runtime_guids.empty())
	{
		Log::message("\tNo runtimes\n");
	}
	else
	{
		Log::message("\tRuntimes:\n");
		for (int i = 0, count = runtime_guids.size(); i < count; ++i)
		{
			const UGUID &rt_guid = runtime_guids[i];
			Log::message("\t\tRuntime #%d is %s: Alias (%s) File GUID (%s)\n", i,
				AssetManager::isRuntimePrimary(rt_guid) ? "primary" : "non-primary",
				AssetManager::getRuntimeAlias(rt_guid).get(), rt_guid.getFileSystemString());
		}
	}
}

Create a New Directory创建一个新目录#

New directory creation, used to manage files within the project.创建新目录,用于管理项目中的文件。

This operation is implemented in the plugin as create_directory() function.该操作在插件中作为create_directory()函数实现。

AssetsPlugin.cpp

源代码 (C++)
// Method creating a new directory
void AssetsPlugin::create_directory()
{
	// making a base path for a new directory and generating a unique virtual path for it
	const StringStack<> &base_path = String::joinPaths(DIRECTORY_FOR_NEW_DIRECTORIES,
		"New Directory");
	const String &new_path = AssetManager::generateUniquePath(base_path);

	// creating a new directory for the specified path and displaying the corresponding notification
	if (AssetManager::createDirectory(new_path))
	{
		success_notification(
			String::format("Directory has been created successfully: \"%s\"", new_path.get()));
		assert(AssetManager::isDirectoryWritable(new_path));
	}
	else
	{
		error_notification(String::format("Can't create directory: \"%s\"", new_path.get()));
	}
}

Remove the Directory with Copied Assets Asynchronously异步删除复制资源的目录#

This type of operation is used when removing of the directory is not critically important for the current execution of the application and can be performed in the background of the working process (e.g. to remove a group of assets from the project). It represents a non-blocking call, the function returns immediately as soon as the operation is put to a queue.当删除目录对应用程序的当前执行不是非常重要,并且可以在工作过程的后台执行(例如从项目中删除一组资源)时,使用这种类型的操作。它表示非阻塞调用,一旦操作被放入队列,该函数立即返回。

注意
Removing a directory containing assets from a project means completely removing all these assets from all other project's elements referring to them. There is only a notification about the start of the process, no information about its status or any errors occurred. To catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method.删除一个目录包含资源从一个项目意味着完全移除所有这些资源从所有其他项目的元素指的是他们。只有一个通知关于过程的开始,没有地位的信息或任何错误发生。捕捉完成操作设置回调通过AssetManager::addProcessEndCallback()方法。

This operation is implemented in the plugin as remove_directory_with_copied_assets_asynchronously() function.这个操作是在插件实现remove_directory_with_copied_assets_asynchronously()函数。

AssetsPlugin.cpp

源代码 (C++)
// Method removing a directory with assets (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::remove_directory_with_copied_assets_asynchronously()
{
	// checking if the specified directory exists (if not, report and return)
	if (!AssetManager::isDirectory(DIRECTORY_FOR_COPIED_ASSETS))
	{
		error_notification(
			String::format("Directory doesn't exist: \"%s\"", DIRECTORY_FOR_COPIED_ASSETS));
		return;
	}

	// putting a new "remove directory" task to a queue and displaying the corresponding notification depending on the result
	if (AssetManager::removeDirectoryAsync(DIRECTORY_FOR_COPIED_ASSETS))
	{
		success_notification(String::format("Directory removal process has started successfully: \"%s\"",
			DIRECTORY_FOR_COPIED_ASSETS));
	}
	else
	{
		error_notification(
			String::format("Can't start directory removal process: \"%s\"", DIRECTORY_FOR_COPIED_ASSETS));
	}
}

Move the Directory with Copied Assets Asynchronously移动目录与异步复制资源#

This type of operation is used when moving of the directory is not critically important for the current execution of the application and can be performed in the background of the working process (e.g. to move a group of assets to another location). It represents a non-blocking call, the function returns immediately as soon as the operation is put to a queue.当移动目录对应用程序的当前执行不是非常重要,并且可以在工作过程的后台执行时(例如,将一组资源移动到另一个位置),就会使用这种类型的操作。它表示非阻塞调用,一旦操作被放入队列,该函数立即返回。

注意
There is only a notification about the start of the process, no information about its status or any errors occurred. To catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method.只有一个通知关于过程的开始,没有地位的信息或任何错误发生。捕捉完成操作设置回调通过AssetManager::addProcessEndCallback()方法。

This operation is implemented in the plugin as move_directory_with_copied_assets_asynchronously() function.该操作在插件中作为move_directory_with_copied_assets_asynchronously()函数实现。

AssetsPlugin.cpp

源代码 (C++)
// Method moving a directory with assets to a new location (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::move_directory_with_copied_assets_asynchronously()
{
	// checking if the source directory exists (if not, report and return)
	if (!AssetManager::isDirectory(DIRECTORY_FOR_COPIED_ASSETS))
	{
		error_notification(
			String::format("Directory doesn't exist: \"%s\"", DIRECTORY_FOR_COPIED_ASSETS));
		return;
	}

	// making a base destination path and generating a unique virtual path for it
	const StringStack<> &base_path = String::joinPaths(DIRECTORY_FOR_NEW_DIRECTORIES,
		"New Directory");
	const String &new_path = AssetManager::generateUniquePath(base_path);

	// putting a new "move directory" task to a queue and displaying the corresponding notification depending on the result
	if (AssetManager::moveDirectoryAsync(DIRECTORY_FOR_COPIED_ASSETS, new_path))
	{
		success_notification(
			String::format("Directory moving process has started successfully: from \"%s\" to \"%s\"",
				DIRECTORY_FOR_COPIED_ASSETS, new_path.get()));
	}
	else
	{
		error_notification(String::format("Can't start directory moving process:  from \"%s\" to \"%s\"",
			DIRECTORY_FOR_COPIED_ASSETS, new_path.get()));
	}
}

Rename the Directory with Copied Assets Asynchronously重命名目录与异步复制资源#

This type of operation is used when renaming of the directory is not critically important for the current execution of the application and can be performed in the background of the working process (e.g. to move a group of assets to another location). It represents a non-blocking call, the function returns immediately as soon as the operation is put to a queue. It is used to manage files. All virtual paths of assets are kept protected.当重命名目录对应用程序的当前执行不是非常重要,并且可以在工作过程的后台执行时(例如,将一组资源移动到另一个位置),就会使用这种类型的操作。它表示非阻塞调用,一旦操作被放入队列,该函数立即返回。用于管理文件。资源的所有虚拟路径都受到保护。

注意
There is only a notification about the start of the process, no information about its status or any errors occurred. To catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method.只有一个通知关于过程的开始,没有地位的信息或任何错误发生。捕捉完成操作设置回调通过AssetManager::addProcessEndCallback()方法。

This operation is implemented in the plugin as rename_directory_with_copied_assets_asynchronously() function.该操作在插件中作为rename_directory_with_copied_assets_asynchronously()函数实现。

AssetsPlugin.cpp

源代码 (C++)
// Method renaming a directory with assets (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::rename_directory_with_copied_assets_asynchronously()
{
	// checking if the specified directory exists (if not, report and return)
	if (!AssetManager::isDirectory(DIRECTORY_FOR_COPIED_ASSETS))
	{
		error_notification(
			String::format("Directory doesn't exist: \"%s\"", DIRECTORY_FOR_COPIED_ASSETS));
		return;
	}

	// generating a unique virtual path for the destination directory and getting its base name to change it
	const String &new_path = AssetManager::generateUniquePath(DIRECTORY_FOR_COPIED_ASSETS);
	const StringStack<> &new_name = new_path.basename();

	// putting a new "rename directory" task to a queue and displaying the corresponding notification depending on the result
	if (AssetManager::renameDirectoryAsync(DIRECTORY_FOR_COPIED_ASSETS, new_name))
	{
		success_notification(
			String::format("Directory renaming process has started successfully: from \"%s\" to \"%s\"",
				DIRECTORY_FOR_COPIED_ASSETS, new_path.get()));
	}
	else
	{
		error_notification(String::format("Can't start directory renaming process: from \"%s\" to \"%s\"",
			DIRECTORY_FOR_COPIED_ASSETS, new_path.get()));
	}
}

Copy the Directory with Copied Assets Asynchronously异步复制目录复制资源#

This type of operation is used when copying of the directory is not critically important for the current execution of the application and can be performed in the background of the working process (e.g. to move a group of assets to another location). It represents a non-blocking call, the function returns immediately as soon as the operation is put to a queue. It is used to manage files. All virtual paths of assets are kept protected.当目录的复制对应用程序的当前执行不是非常重要,并且可以在工作过程的后台执行时(例如,将一组资源移动到另一个位置),就会使用这种类型的操作。它表示非阻塞调用,一旦操作被放入队列,该函数立即返回。用于管理文件。资源的所有虚拟路径都受到保护。

注意
There is only a notification about the start of the process, no information about its status or any errors occurred. To catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method.只有一个通知关于过程的开始,没有地位的信息或任何错误发生。捕捉完成操作设置回调通过AssetManager::addProcessEndCallback()方法。

This operation is implemented in the plugin as copy_directory_with_copied_assets_asynchronously() function.该操作在插件中作为copy_directory_with_copied_assets_asynchronously()函数实现。

AssetsPlugin.cpp

源代码 (C++)
// Method copying a directory with assets to the specified path (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::copy_directory_with_copied_assets_asynchronously()
{
	// checking if the specified directory exists (if not, report and return)
	if (!AssetManager::isDirectory(DIRECTORY_FOR_COPIED_ASSETS))
	{
		error_notification(
			String::format("Directory doesn't exist: \"%s\"", DIRECTORY_FOR_COPIED_ASSETS));
		return;
	}

	// making the destination base path, to which the directory is to be copied, and generating a new unique virtual path for it
	const StringStack<> &base_path = String::joinPaths(DIRECTORY_FOR_NEW_DIRECTORIES,
		"New Directory");
	const String &new_path = AssetManager::generateUniquePath(base_path);

	// launching the copying process in the background (asynchronously, without blocking the main thread) and displaying a notification, if it has started successfully
	// to catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method (see method "process_end()" here)
	if (AssetManager::copyDirectoryAsync(DIRECTORY_FOR_COPIED_ASSETS, new_path))
	{
		success_notification(
			String::format("Directory copying process has started successfully: from \"%s\" to \"%s\"",
				DIRECTORY_FOR_COPIED_ASSETS, new_path.get()));
	}
	else
	{
		error_notification(String::format("Can't start directory copying process: from \"%s\" to \"%s\"",
			DIRECTORY_FOR_COPIED_ASSETS, new_path.get()));
	}
}

Print All Known Directory Paths打印所有已知目录路径#

Lists all directory paths known to the Asset Manager, this can be used to iterate over all directories in the project. The operation is implemented in the plugin as print_all_known_directories() function.列出所有已知Asset Manager目录路径,这可以用来遍历所有项目的目录。插件实现的操作print_all_known_directories()函数。

AssetsPlugin.cpp

源代码 (C++)
// Method listing all directories known to the AssetManager
void AssetsPlugin::print_all_known_directories()
{
	Log::message("\n[Assets Plugin] All directory paths:\n");
	for (const String &path : AssetManager::getDirectoryPathsAll())
	{
		Log::message("\t%s\n", path.get());
	}
	Log::message("\n");
}

Print All Directory Paths for Specified Directory打印指定目录下的所有目录路径#

Lists all subdirectories for the specified directory. Can be used to iterate over subdirectories of the specified directory (e.g. when processing a well-structured pack of assets sorted by folders according to their types, usage logic, or otherwise). This operation is implemented in the plugin as print_directories_from_test_directory() function.列出指定目录的所有子目录。可用于遍历指定目录的子目录(例如,在处理结构良好的资源包时,根据文件夹的类型、使用逻辑或其他方式对其进行排序)。 该操作在插件中作为print_directories_from_test_directory()函数实现。

AssetsPlugin.cpp

源代码 (C++)
// Method listing all subdirectories of the specified path, that are known to the AssetManager
void AssetsPlugin::print_directories_from_test_directory()
{
	Log::message("\n[Assets Plugin] Directory paths from \"%s\":\n", DIRECTORY_FOR_NEW_DIRECTORIES);
	for (const String &path : AssetManager::getDirectoryPaths(DIRECTORY_FOR_NEW_DIRECTORIES))
	{
		Log::message("\t%s\n", path.get());
	}
	Log::message("\n");
}

Create a Mount Point创建挂载点#

This operation can be used to add a link to an external folder, shared storage or package (e.g. a library of 3D models, VFX, etc.) to the project.此操作可用于添加一个链接到一个外部文件夹,共享存储或包(例如一个3 d模型库、视效、等等)的项目。

The parameters of the mount point are managed via an instance of the MountPointParameters class - first, we should create an instance and specify necessary settings (path, access mode, etc.) and then tell the AssetManager to create a mount point via the createMountPoint() method. A corresponding .umount file is created in the project's data folder. This is a file in the JSON format that represents the root mount point.挂载点的参数是通过MountPointParameters类的一个实例来管理的——首先,我们应该创建一个实例并指定必要的设置(路径、访问模式等),然后通过createMountPoint()方法告诉AssetManager创建一个挂载点。在项目的data文件夹中创建了一个相应的.umount文件。这是一个JSON格式的文件,表示根挂载点。

This operation is implemented in the plugin as create_mount_point() function.这个操作是在插件实现create_mount_point()函数。

AssetsPlugin.cpp

源代码 (C++)
// Method creating a new mount point
void AssetsPlugin::create_mount_point()
{
	// checking if a path for a mount point to be created already exists and displaying the corresponding notifications
	if (AssetManager::isExist(DIRECTORY_FOR_MOUNT_POINT))
	{
		if (AssetManager::isAsset(DIRECTORY_FOR_MOUNT_POINT))
		{
			error_notification(
				String::format("Target path already contains a known asset: \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
		}
		else if (AssetManager::isMountPoint(DIRECTORY_FOR_MOUNT_POINT))
		{
			assert(AssetManager::isDirectory(DIRECTORY_FOR_MOUNT_POINT));	// Mount point is a directory
			success_notification(
				String::format("Mount point for the target path already exists: \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
		}
		else if (AssetManager::isDirectory(DIRECTORY_FOR_MOUNT_POINT))
		{
			error_notification(
				String::format("Target path already contains a known directory: \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
		}
		else
		{
			// May be some reserved file or directory already exists there
			error_notification(
				String::format("Target path already exists and may contain a reserved file or some directory: \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
		}
		return;
	}

	// making an absolute path to the mount
	StringStack<> abs_dir_path = String::normalizeDirPath(Engine::get()->getDataPath());
	abs_dir_path = abs_dir_path.trimLast("/");
	abs_dir_path = abs_dir_path.dirname();
	abs_dir_path = String::joinPaths(abs_dir_path, MOUNT_POINT_DIRECTORY_NAME);

	// getting an existing directory for the absolute path or creating it
	if (!Dir::mkdir(abs_dir_path))
	{
		error_notification(String::format("Can't create directory \"%s\"", abs_dir_path.get()));
		return;
	}

	// creating an instance of the MountPointParameters class to specify necessary settings for a new mount point to be created (path and access mode) 
	MountPointParametersPtr mount_params = MountPointParameters::create();
	mount_params->setAbsolutePath(abs_dir_path);
	mount_params->setAccess(MountPointParameters::ACCESS_READWRITE);

	// trying to create a new mount point for the specified path and parameters, displaying a notification depending on the result
	if (AssetManager::createMountPoint(DIRECTORY_FOR_MOUNT_POINT, mount_params))
	{
		success_notification(String::format("Mount point has been created successfully: \"%s\"",
			DIRECTORY_FOR_MOUNT_POINT));
		assert(AssetManager::isMountPoint(DIRECTORY_FOR_MOUNT_POINT));
	}
	else
	{
		error_notification(
			String::format("Can't create mount point: \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
	}
}

Remove a Mount Point移除挂载点#

This operation can be used to disconnect from a shared storage.这个操作可以用来断开从共享存储。

注意
Removing a mount point containing assets used in the project means completely removing all these assets from all other project's elements referring to them.删除包含项目中使用的资源的挂载点意味着从引用它们的所有其他项目元素中完全删除所有这些资源。

This operation is implemented in the plugin as remove_mount_point() function.这个操作是在插件实现remove_mount_point()函数。

AssetsPlugin.cpp

源代码 (C++)
// Method removing a mount point
void AssetsPlugin::remove_mount_point()
{
	// checking if the specified mount point exists
	if (!AssetManager::isMountPoint(DIRECTORY_FOR_MOUNT_POINT))
	{
		error_notification(
			String::format("Mount point doesn't exist \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
		return;
	}

	// trying to remove the specified mount point via the AssetManager, displaying a notification depending on the result
	if (AssetManager::removeMountPoint(DIRECTORY_FOR_MOUNT_POINT))
	{
		success_notification(String::format("Mount point has been removed successfully: \"%s\"",
			DIRECTORY_FOR_MOUNT_POINT));
	}
	else
	{
		error_notification(
			String::format("Can't remove mount point: \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
	}
}

Print Mount Point Information挂载点打印信息#

This operation can be used to obtain information about a mount point (virtual path, absolute path, access, filters). The parameters of the mount point are managed via an instance of the MountPointParameters class - first, we call the getMountPointParameters() method to fill this instance with necessary data and then use the instance to print information. This is a sample code demonstrating the use of public API.这个操作可以用来获取信息挂载点(虚拟路径,绝对路径、访问过滤器)。挂载点的参数管理通过MountPointParameters类的一个实例——首先,我们称之为getMountPointParameters()方法来填补这一实例提供必要的数据,然后使用实例打印信息。这是一个示例代码展示公共API的使用。

This operation is implemented in the plugin as print_mount_point_information() function.该操作在插件中作为print_mount_point_information()函数实现。

AssetsPlugin.cpp

源代码 (C++)
// Method printing complete information about the mount point (virtual path, access, and filters)
void AssetsPlugin::print_mount_point_information()
{
	// checking if the specified mount point exists
	if (!AssetManager::isMountPoint(DIRECTORY_FOR_MOUNT_POINT))
	{
		Log::message("[Assets Plugin] Mount point doesn't exist: \"%s\"\n",
			DIRECTORY_FOR_MOUNT_POINT);
		return;
	}

	Log::message("[Assets Plugin] Mount point information\n\n");
	Log::message("\tVirtual path: \"%s\"\n", DIRECTORY_FOR_MOUNT_POINT);

	// retrieving parameters of the specified mount point to a MountPointParameters class instance and print information using its data
	const MountPointParametersPtr mount_params = AssetManager::getMountPointParameters(
		DIRECTORY_FOR_MOUNT_POINT);

	Log::message("\tAbsolute path: \"%s\"\n", mount_params->getAbsolutePath());
	Log::message("\tAccess: \"%s\"\n",
		(mount_params->getAccess() == MountPointParameters::ACCESS_READONLY) ? "read-only"
																			 : "read & write");

	// retrieving exclusive filters of the specified mount point to a vector and printing its contents or a corresponding message in case filters are not set
	const Vector<String> &exclusive_filters = mount_params->getExclusiveFilters();
	if (exclusive_filters.empty())
	{
		Log::message("\tNo exclusive filters\n");
	}
	else
	{
		Log::message("\tExclusive filters: %s\n", convert_vector_to_flat_string(exclusive_filters));
	}

	// retrieving ignore filters of the specified mount point to a vector and printing its contents or a corresponding message in case filters are not set
	const Vector<String> &ignore_filters = mount_params->getIgnoreFilters();
	if (ignore_filters.empty())
	{
		Log::message("\tNo ignore filters\n");
	}
	else
	{
		Log::message("\tIgnore filters: %s\n", convert_vector_to_flat_string(ignore_filters));
	}

	Log::message("\n");
}

Reimport Selected Landscape Map重新导入选定的景观地图#

This opreation reimports the selected Landscape Map asset after changing its import parameters. Can be used to reimport an asset existing in the project with a new set of import parameters.该操作在更改所选的Landscape Map资源的导入参数后重新导入它。可用于使用一组新的导入参数重新导入项目中现有的资源。

This operation is implemented in the plugin as reimport_selected_landscape_map() function. This function uses the auxiliary function find_selected_lmap_file() which iterates over the current selection and finds the path to the .lmap asset to be reimported.这个操作是在插件实现reimport_selected_landscape_map()函数。这个函数使用了辅助函数find_selected_lmap_file()遍历当前选择和发现的路径.lmap湾区(资源。

AssetsPlugin.cpp

源代码 (C++)
// Method reimporting the selected Landscape Map (.lmap) asset
void AssetsPlugin::reimport_selected_landscape_map()
{
	// trying to get an .lmap-file in the current selection
	const String &lmap_path = find_selected_lmap_file();
	if (lmap_path.empty())
	{
		return;
	}

	// getting current import parameters for the .lmap asset to an instance of the Collection class
	CollectionPtr source_import_parameters = AssetManager::getAssetImportParameters(lmap_path);
	LandscapeMapImportParametersPtr lmap_import_parameters = LandscapeMapImportParameters::create();
	lmap_import_parameters->load(source_import_parameters);

	// switching the data filling mode import parameter for the asset
	using DataMode = LandscapeMapImportParameters::DATA_FILLING_MODE;
	DataMode mode = lmap_import_parameters->getDataFillingMode();
	switch (mode)
	{
	case DataMode::DATA_FILLING_MODE_FROM_TILESET: mode = DataMode::DATA_FILLING_MODE_MANUAL; break;
	case DataMode::DATA_FILLING_MODE_MANUAL: mode = DataMode::DATA_FILLING_MODE_FROM_TILESET; break;
	}
	lmap_import_parameters->setDataFillingMode(mode);

	// saving new import parameters and using them to reimport asset asynchronously with corresponding notification on starting the process
	CollectionPtr dest_import_parameters = lmap_import_parameters->save();
	if (AssetManager::reimportAssetAsync(lmap_path, dest_import_parameters))
	{
		success_notification(String::format(
			"Asset reimport process has started successfully: \"%s\"", lmap_path.get()));
	}
	else
	{
		error_notification(
			String::format("Can't start asset reimport process: \"%s\"", lmap_path.get()));
	}
}

// auxiliary method setting the current Editor selection to an asset having the specified path
void AssetsPlugin::select_asset(const char *asset_path)
{
	// checking if an asset with the specified path exists and getting its guid
	assert(AssetManager::isAsset(asset_path));
	const UGUID &asset_guid = AssetManager::getAssetGUIDFromPath(asset_path);
	if (asset_guid.isEmpty())
	{
		error_notification(String::format("Invalid path for asset: \"%s\"", asset_path));
		return;
	}

	// setting the current Editor selection to an asset with the specified guid
	SelectionAction::applySelection(SelectorGUIDs::createRuntimesSelector({asset_guid}));
}

// auxiliary method displaying a success message
void AssetsPlugin::success_notification(const char *message) const
{
	notification(message, Math::vec4_green);
}

// auxiliary method displaying an error message
void AssetsPlugin::error_notification(const char *message) const
{
	notification(message, Math::vec4_red);
}

// auxiliary method displaying a generic notification message using the specified color
void AssetsPlugin::notification(const char *message, const Math::vec4 &color) const
{
	if (label_notification_)
	{
		label_notification_->setFontColor(color);
		label_notification_->setText(message);
	}
	Log::message(color, "[Assets Plugin] %s\n", message);
}

// Auxiliary method creating an asset with the specified destination path for the specified source path
bool AssetsPlugin::create_asset(const char *source_path, const char *dest_path) const
{
	// retrieving a path to destination directory and creating it via the AssetManager (the corresponding callback shall be executed)
	const StringStack<> &dir_path = String::dirname(dest_path);
	if (!AssetManager::createDirectory(dir_path))
	{
		error_notification(String::format("Can't create directory: \"%s\"", dir_path.get()));
		return false;
	}

	// opening the source file for reading data
	FilePtr source_asset = File::create(source_path, "rb");
	if (!source_asset->isOpened())
	{
		error_notification(String::format("Can't open asset for reading: \"%s\"", source_path));
		return false;
	}

	// opening the destination asset file for writing data
	FilePtr dest_asset = File::create(dest_path, "wb");
	if (!dest_asset->isOpened())
	{
		error_notification(String::format("Can't open asset for writing: \"%s\"", dest_path));
		return false;
	}

	// declaring a 32Kb buffer for transferring data 
	const size_t MEMORY_SIZE = 1024 * 32;
	Vector<unsigned char> memory(MEMORY_SIZE);
	// reading and writing data from the source file to the destination asset
	while (0 == source_asset->eof())
	{
		const size_t bytes_read = source_asset->read(&memory[0], memory.size());
		const size_t bytes_written = dest_asset->write(&memory[0], bytes_read);
		if (bytes_read != bytes_written)
		{
			dest_asset->close();
			dest_asset.clear();
			Dir::remove(dest_path);
			error_notification(String::format("Can't write data to asset: \"%s\"", dest_path));
			return false;
		}
	}

	return true;
}

// Auxiliary method iterating over the current selection and finds the path to the first .lmap asset in the current Editor selection.
String AssetsPlugin::find_selected_lmap_file() const
{
	String lmap_path;

	// getting guids of runtimes for all currently selected assets, returning an empty string if nothing is selected
	SelectorGUIDs *selector = Selection::getSelectorRuntimes();
	if (!selector || selector->empty())
	{
		return lmap_path;
	}

	// getting guids of all currently selected assets
	const Vector<UGUID> &selected_guids = selector->guids();
	for (const UGUID &guid : selected_guids)
	{
		// obtaining asset path by its GUID and checking if the path corresponds to a registered asset
		String virtual_path = AssetManager::getAssetPathFromGUID(guid);
		if (!AssetManager::isAsset(virtual_path))
		{
			Log::message("[Assets Plugin] Invalid asset path: %s\n", virtual_path.get());
			continue;
		}

		// getting an extension for the asset file and return the virtual path if it is an .lmap asset
		const StringStack<> &extension = virtual_path.extension().getLower();
		if (!String::equal(extension, "lmap"))
		{
			continue;
		}

		lmap_path = std::move(virtual_path);
		break;
	}

	return lmap_path;
}

// Callback function to be executed on adding a new asset
void AssetsPlugin::asset_added(const char *path)
{
	Log::message("[Assets Plugin] Asset added: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed before removing an asset
void AssetsPlugin::asset_before_remove(const char *path)
{
	Log::message("[Assets Plugin] Asset before removing: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed after removing an asset
void AssetsPlugin::asset_removed(const char *path)
{
	Log::message("[Assets Plugin] Asset removed: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed on changing an asset
void AssetsPlugin::asset_changed(const char *path)
{
	Log::message("[Assets Plugin] Asset changed: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed on moving an asset to a new location
void AssetsPlugin::asset_moved(const char *path_from, const char *path_to)
{
	Log::message("[Assets Plugin] Asset moved from \"%s\" to \"%s\"\n", path_from, path_to);
	// Add your custom handling code here...
}

// Callback function to be executed on adding a new directory
void AssetsPlugin::directory_added(const char *path)
{
	Log::message("[Assets Plugin] Directory added: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed before removing a directory
void AssetsPlugin::directory_before_remove(const char *path)
{
	Log::message("[Assets Plugin] Directory before removing: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed after removing a directory
void AssetsPlugin::directory_removed(const char *path)
{
	Log::message("[Assets Plugin] Directory removed: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed on moving a directory to a new location
void AssetsPlugin::directory_moved(const char *path_from, const char *path_to)
{
	Log::message("[Assets Plugin] Directory moved from \"%s\" to \"%s\"\n", path_from, path_to);
	// Add your custom handling code here...
}

// Callback function to be executed on beginning of asynchronous asset processing by the Asset Manager (e.g., validation, import, reimport, removal, copying, conversion, migration etc.)
void AssetsPlugin::process_begin()
{
	Log::message("[Assets Plugin] Processing started\n");
	// Add your custom handling code here...
}

// Callback function to be executed on completion of asynchronous asset processing by the Asset Manager (e.g., validation, import, reimport, removal, copying, conversion, migration etc.)
void AssetsPlugin::process_end()
{
	Log::message("[Assets Plugin] Processing completed\n");
	// Add your custom handling code here...
}

// Callback function to be executed on changing selection in the Editor
void AssetsPlugin::selection_changed()
{
	// trying to get an .lmap-file in the current selection
	const String &lmap_path = find_selected_lmap_file();

	// updating the button text and status depending on the result
	const char BUTTON_LMAP_REIMPORT_NAME[] = "Reimport Landscape Map";
	const StringStack<> &button_name = String::format("%s%s%s", BUTTON_LMAP_REIMPORT_NAME,
		lmap_path.empty() ? "" : " - ", lmap_path.get());
	button_lmap_reimporting_->setText(button_name);
	button_lmap_reimporting_->setEnabled(lmap_path.size() > 0);
}

Complete Source Code完整的源代码#

The complete source code of the Assets plugin with comments is given below.Assets插件的完整源代码注释下面。

AssetsPlugin.h

源代码 (C++)
#pragma once

#include <editor/UniginePlugin.h>
#include <editor/UnigineEngineGuiWindow.h>

#include <UnigineVector.h>
#include <UnigineWidgets.h>

#include "CallbackGuard.h"

class QAction;

class AssetsPlugin final
	: public QObject
	, public ::UnigineEditor::Plugin
{
	Q_OBJECT
	Q_DISABLE_COPY(AssetsPlugin)
	Q_PLUGIN_METADATA(IID UNIGINE_EDITOR_PLUGIN_IID FILE "AssetsPlugin.json")
	Q_INTERFACES(UnigineEditor::Plugin)

public:
	AssetsPlugin() = default;

	bool init() override;
	void shutdown() override;

private:
	void activate();
	void deactivate();
	bool is_activated() const { return static_cast<bool>(window_); }

	void create_window();
	void destroy_window();
	// Auxiliary method creating the GUI of the Assets Plugin
	void build_gui();
	// Auxiliary method adding button elements to the GUI of the Assets Plugin
	void add_buttons(const Unigine::WidgetWindowPtr &main_window);
	// Auxiliary method setting up window title and update policy
	void setup_window();

	// Auxiliary method setting up callbacks for various events (assets, directories, processing)
	void make_connections_to_asset_manager();
	// Auxiliary methods removing added AssetManager callbacks
	void destroy_connections_from_asset_manager();

	// Auxiliary method setting up a callback for tracking selection changes in the UnigineEditor
	void make_connection_to_selection();
	// Auxiliary methods removing added selection callbacks
	void destroy_connection_from_selection();

	// Method importing a new asset and waiting for the process to complete (blocking the thread)
	void import_new_asset_synchronously();
	// Method importing an asset in the background (non-blocking, returns immediately without waiting for operation completion)
	void import_new_asset_asynchronously();
	// Method removing an asset in the background (non-blocking, returns immediately without waiting for operation completion)
	void remove_asset_asynchronously();
	// Method moving an asset in the background (non-blocking, returns immediately without waiting for operation completion)
	void move_asset_asynchronously();
	// Method renaming an asset in the background (non-blocking, returns immediately without waiting for operation completion)
	void rename_asset_asynchronously();
	
	// Method copying an asset in the background (non-blocking, returns immediately without waiting for operation completion)
	void copy_asset_asynchronously();
	// Method printing out the list of paths and GUIDs for all assets in the root project directory (including all subdirectories)
	void print_all_known_assets();
	// Method printing out general information for the specified asset path
	void print_asset_information(const char *asset_path);

	// Method creating a new directory
	void create_directory();
	// Method removing a directory with assets (non-blocking, returns immediately without waiting for operation completion)
	void remove_directory_with_copied_assets_asynchronously();
	// Method moving a directory with assets to a new location (non-blocking, returns immediately without waiting for operation completion)
	void move_directory_with_copied_assets_asynchronously();
	// Method renaming a directory with assets (non-blocking, returns immediately without waiting for operation completion)
	void rename_directory_with_copied_assets_asynchronously();
	// Method copying a directory with assets to the specified path (non-blocking, returns immediately without waiting for operation completion)
	void copy_directory_with_copied_assets_asynchronously();
	// Method listing all directories known to the AssetManager
	void print_all_known_directories();
	// Method listing all subdirectories for the specified path, that are known to the AssetManager
	void print_directories_from_test_directory();

	// Method creating a new mount point
	void create_mount_point();
	// Method removing a mount point
	void remove_mount_point();
	// Method printing complete information about the mount point (virtual path, access, and filters)
	void print_mount_point_information();

	// Method reimporting the selected Landscape Map (.lmap) asset
	void reimport_selected_landscape_map();

	// Auxiliary method setting the current Editor selection to an asset having the specified path
	void select_asset(const char *asset_path);

	// Auxiliary method displaying a success message
	void success_notification(const char *message) const;
	// Auxiliary method displaying an error message
	void error_notification(const char *message) const;
	// Auxiliary method displaying a generic notification message using the specified color
	void notification(const char *message, const Unigine::Math::vec4 &color) const;

	// Method creating and importing an asset synchronously (blocking the thread until the operation is completed)
	Unigine::String create_and_import_new_asset_synchronously() const;
	// Auxiliary method creating an asset with the specified destination path for the specified source path
	bool create_asset(const char *source_path, const char *dest_path) const;
	// Auxiliary method iterating over the current selection and finds the path to the first .lmap asset in the current Editor selection.
	Unigine::String find_selected_lmap_file() const;

	// Callbacks from AssetManager:

	// Callback function to be executed on adding a new asset
	void asset_added(const char *path);
	// Callback function to be executed before removing an asset
	void asset_before_remove(const char *path);
	// Callback function to be executed after removing an asset
	void asset_removed(const char *path);
	// Callback function to be executed on changing an asset
	void asset_changed(const char *path);
	// Callback function to be executed on moving an asset to a new location
	void asset_moved(const char *path_from, const char *path_to);
	// Callback function to be executed on adding a new directory
	void directory_added(const char *path);
	// Callback function to be executed before removing a directory
	void directory_before_remove(const char *path);
	// Callback function to be executed after removing a directory
	void directory_removed(const char *path);
	// Callback function to be executed on moving a directory to a new location
	void directory_moved(const char *path_from, const char *path_to);
	// Callback function to be executed on beginning of asynchronous asset processing by the Asset Manager (e.g., validation, import, reimport, removal, copying, conversion, migration etc.)
	void process_begin();
	// Callback function to be executed on completion of asynchronous asset processing by the Asset Manager (e.g., validation, import, reimport, removal, copying, conversion, migration etc.)
	void process_end();

	// Callback from Selection:

	// Callback function to be executed on changing selection in the Editor
	void selection_changed();

private:
	QAction *action_{};
	::UnigineEditor::EngineGuiWindow *window_{};
	Unigine::GuiPtr gui_;
	Unigine::WidgetLabelPtr label_notification_;
	Unigine::WidgetButtonPtr button_lmap_reimporting_;

	Unigine::Vector<utils::CallbackGuardUPtr> callbacks_;
	QMetaObject::Connection connection_selection_;
};

AssetsPlugin.cpp

源代码 (C++)
#include "AssetsPlugin.h"

#include <UnigineDir.h>
#include <UnigineEngine.h>
#include <UnigineFileSystem.h>
#include <UnigineStreams.h>

#include <editor/UnigineActions.h>
#include <editor/UnigineAssetManager.h>
#include <editor/UnigineAssetImportParameters.h>
#include <editor/UnigineConstants.h>
#include <editor/UnigineWindowManager.h>
#include <editor/UnigineSelection.h>
#include <editor/UnigineSelector.h>

#include <QMenu>

using namespace Unigine;
using namespace UnigineEditor;

namespace
{
// test directories to be used
const char DIRECTORY_FOR_TESTING[] = "Assets Plugin - Tests";
const char DIRECTORY_FOR_NEW_ASSETS[] = "Assets Plugin - Tests/Textures";
const char DIRECTORY_FOR_MOVED_ASSETS[] = "Assets Plugin - Tests/Moved Assets";
const char DIRECTORY_FOR_COPIED_ASSETS[] = "Assets Plugin - Tests/Copied Assets";
const char DIRECTORY_FOR_NEW_DIRECTORIES[] = "Assets Plugin - Tests/Directories";
const char DIRECTORY_FOR_MOUNT_POINT[] = "Assets Plugin - Tests/Mount Point";

// name of the directory to be mounted
const char MOUNT_POINT_DIRECTORY_NAME[] = "Assets Plugin - Test Mount Point";

// path to a test asset
const char TEXTURE_PATH[] = "core/textures/world_light/celestial_body_moon.png";

String convert_vector_to_flat_string(const Vector<String> &v)
{
	String result;
	const int size = std::accumulate(v.begin(), v.end(), 0,
		[](int val, const String &s) { return val + s.size() + 3; });
	result.reserve(size + 1);
	for (const String &s : v)
	{
		result += "\"";
		result += s;
		result += "\" ";
	}
	return result;
}

} // anonymous namespace

bool AssetsPlugin::init()
{
	assert(AssetManager::isInitialized());

	// checking if the test asset file exists
	if (!FileSystem::isFileExist(TEXTURE_PATH))
	{
		Log::error("[Assets Plugin] The required resource is missing: %s\n", TEXTURE_PATH);
	}

	// adding an item for the Assets Plugin to the Windows menu of the UnigineEditor
	QMenu *menu = WindowManager::findMenu(Constants::MM_WINDOWS);
	action_ = menu->addAction("Assets Plugin", this, &AssetsPlugin::activate);

	connect(WindowManager::instance(), &WindowManager::windowHidden, this, [this](QWidget *w) {
		if (w == window_)
		{
			deactivate();
		}
	});

	return true;
}

void AssetsPlugin::shutdown()
{
	disconnect(WindowManager::instance(), nullptr, this, nullptr);

	delete action_;
	action_ = nullptr;

	deactivate();
}

void AssetsPlugin::activate()
{
	if (is_activated())
	{
		WindowManager::show(window_);
		return;
	}

	create_window();
	make_connections_to_asset_manager();
	make_connection_to_selection();
}

void AssetsPlugin::deactivate()
{
	destroy_connection_from_selection();
	destroy_connections_from_asset_manager();
	destroy_window();
}

void AssetsPlugin::create_window()
{
	assert(nullptr == window_);
	window_ = new EngineGuiWindow;

	setup_window();
	build_gui();

	// To refresh button states
	selection_changed();

	WindowManager::add(window_, WindowManager::NEW_FLOATING_AREA);
	WindowManager::show(window_);
}

void AssetsPlugin::destroy_window()
{
	label_notification_.clear();
	button_lmap_reimporting_.clear();
	gui_.clear();

	if (window_)
	{
		window_->clearCallbacks();
		WindowManager::remove(window_);
		delete window_;
		window_ = nullptr;
	}
}

// Auxiliary method creating the GUI of the Assets Plugin
void AssetsPlugin::build_gui()
{
	gui_ = window_->getGui();

	WidgetWindowPtr main_window = WidgetWindow::create(gui_, "Assets Window");
	gui_->addChild(main_window, Gui::ALIGN_OVERLAP);

	constexpr int DEFAULT_WIDTH = 800;
	constexpr int DEFAULT_HEIGHT = 400;
	main_window->setWidth(DEFAULT_WIDTH);
	main_window->setHeight(DEFAULT_HEIGHT);
	main_window->setSizeable(true);

	add_buttons(main_window);

	label_notification_ = WidgetLabel::create(gui_);
	main_window->addChild(label_notification_, Gui::ALIGN_EXPAND);
}

// Auxiliary method adding button elements to the GUI of the Assets Plugin
void AssetsPlugin::add_buttons(const WidgetWindowPtr &main_window)
{
	auto create_button = [&main_window, this](const char *name, void (AssetsPlugin::*p)()) {
		WidgetButtonPtr button = WidgetButton::create(gui_, name);
		button->addCallback(Gui::CLICKED, MakeCallback(this, p));
		main_window->addChild(button);
		return button;
	};

	// assets
	create_button("Import new asset synchronously", &AssetsPlugin::import_new_asset_synchronously);
	create_button("Import new asset asynchronously",
		&AssetsPlugin::import_new_asset_asynchronously);
	create_button("Remove asset", &AssetsPlugin::remove_asset_asynchronously);
	create_button("Move asset", &AssetsPlugin::move_asset_asynchronously);
	create_button("Rename asset", &AssetsPlugin::rename_asset_asynchronously);
	create_button("Copy asset", &AssetsPlugin::copy_asset_asynchronously);
	create_button("Print all known assets", &AssetsPlugin::print_all_known_assets);
	create_button("Create directory", &AssetsPlugin::create_directory);
	create_button("Remove directory with copied assets",
		&AssetsPlugin::remove_directory_with_copied_assets_asynchronously);
	create_button("Move directory with copied assets",
		&AssetsPlugin::move_directory_with_copied_assets_asynchronously);
	create_button("Rename directory with copied assets",
		&AssetsPlugin::rename_directory_with_copied_assets_asynchronously);
	create_button("Copy directory with copied assets",
		&AssetsPlugin::copy_directory_with_copied_assets_asynchronously);
	create_button("Print all known directories", &AssetsPlugin::print_all_known_directories);
	StringStack<> button_name = String::format("Print all known directories from \"%s\"",
		DIRECTORY_FOR_NEW_DIRECTORIES);
	create_button(button_name, &AssetsPlugin::print_directories_from_test_directory);
	create_button("Create mount point", &AssetsPlugin::create_mount_point);
	create_button("Remove mount point", &AssetsPlugin::remove_mount_point);
	create_button("Print mount point information", &AssetsPlugin::print_mount_point_information);

	button_lmap_reimporting_ = create_button("", &AssetsPlugin::reimport_selected_landscape_map);
}

// Auxiliary method setting up window title and update policy
void AssetsPlugin::setup_window()
{
	window_->setWindowTitle("Plugin - Assets");
	// setting a policy to update window when it's visible to ensure that the status of the Landscape Map Reimport button is refreshed as we select an asset
	window_->setRenderingPolicy(EngineGuiWindow::RENDERING_POLICY_WINDOW_VISIBLE);
}

// Auxiliary method setting up callbacks for various events (assets, directories, processing)
void AssetsPlugin::make_connections_to_asset_manager()
{
	using std::make_unique;
	using utils::CallbackGuard;
	using AM = AssetManager;

	assert(callbacks_.empty());
	constexpr int RESERVED_NUM_CALLBACKS = 16;
	callbacks_.reserve(RESERVED_NUM_CALLBACKS);
	// assets
	callbacks_.push_back(make_unique<CallbackGuard>(
		AM::addAssetAddedCallback(MakeCallback(this, &AssetsPlugin::asset_added)),
		[](void *p) { AM::removeAssetAddedCallback(p); }));
	callbacks_.push_back(make_unique<CallbackGuard>(
		AM::addAssetBeforeRemoveCallback(MakeCallback(this, &AssetsPlugin::asset_before_remove)),
		[](void *p) { AM::removeAssetBeforeRemoveCallback(p); }));
	callbacks_.push_back(make_unique<CallbackGuard>(
		AM::addAssetRemovedCallback(MakeCallback(this, &AssetsPlugin::asset_removed)),
		[](void *p) { AM::removeAssetRemovedCallback(p); }));
	callbacks_.push_back(make_unique<CallbackGuard>(
		AM::addAssetChangedCallback(MakeCallback(this, &AssetsPlugin::asset_changed)),
		[](void *p) { AM::removeAssetChangedCallback(p); }));
	callbacks_.push_back(make_unique<CallbackGuard>(
		AM::addAssetMovedCallback(MakeCallback(this, &AssetsPlugin::asset_moved)),
		[](void *p) { AM::removeAssetMovedCallback(p); }));
	// directories
	callbacks_.push_back(make_unique<CallbackGuard>(
		AM::addDirectoryAddedCallback(MakeCallback(this, &AssetsPlugin::directory_added)),
		[](void *p) { AM::removeDirectoryAddedCallback(p); }));
	callbacks_.push_back(make_unique<CallbackGuard>(
		AM::addDirectoryBeforeRemoveCallback(MakeCallback(this, &AssetsPlugin::directory_before_remove)),
		[](void *p) { AM::removeDirectoryBeforeRemoveCallback(p); }));
	callbacks_.push_back(make_unique<CallbackGuard>(
		AM::addDirectoryRemovedCallback(MakeCallback(this, &AssetsPlugin::directory_removed)),
		[](void *p) { AM::removeDirectoryRemovedCallback(p); }));
	callbacks_.push_back(make_unique<CallbackGuard>(
		AM::addDirectoryMovedCallback(MakeCallback(this, &AssetsPlugin::directory_moved)),
		[](void *p) { AM::removeDirectoryMovedCallback(p); }));
	// processing
	callbacks_.push_back(make_unique<CallbackGuard>(
		AM::addProcessBeginCallback(MakeCallback(this, &AssetsPlugin::process_begin)),
		[](void *p) { AM::removeProcessBeginCallback(p); }));
	callbacks_.push_back(make_unique<CallbackGuard>(
		AM::addProcessEndCallback(MakeCallback(this, &AssetsPlugin::process_end)),
		[](void *p) { AM::removeProcessEndCallback(p); }));
}

// Auxiliary method setting up a callback for tracking selection changes in the UnigineEditor
void AssetsPlugin::make_connection_to_selection()
{
	Selection *selection = Selection::instance();
	connection_selection_ = connect(selection, &Selection::changed, this,
		&AssetsPlugin::selection_changed);
}

// Auxiliary methods removing added callbacks
void AssetsPlugin::destroy_connection_from_selection()
{
	disconnect(connection_selection_);
}

void AssetsPlugin::destroy_connections_from_asset_manager()
{
	callbacks_.clear();
}
// Method importing a new asset and waiting for the process to complete (blocking the thread)
void AssetsPlugin::import_new_asset_synchronously()
{
	// calling an auxiliary function to create a new asset and import it, displaying information on imported asset on success, or an error notification
	String result = create_and_import_new_asset_synchronously();
	if (!result.empty())
	{
		success_notification(
			String::format("Asset has imported successfully: \"%s\"", result.get()));

		// set current Editor selection to the imported asset and print information about it
		select_asset(result);

		print_asset_information(result);
	}
	else
	{
		error_notification(String::format("Can't import asset: \"%s\"", result.get()));
	}
}

// Method creating and importing an asset synchronously (blocking the thread until the operation is completed)
String AssetsPlugin::create_and_import_new_asset_synchronously() const
{
	String result;

	// making a base path for a new asset to be imported
	const StringStack<> &base_asset_path = String::joinPaths(DIRECTORY_FOR_NEW_ASSETS,
		String::basename(TEXTURE_PATH));

	// asking  the Asset Manager to generate a unique 
	String new_texture_path = AssetManager::generateUniquePath(base_asset_path);
	if (!create_asset(TEXTURE_PATH, new_texture_path))
	{
		return result;
	}
	// waiting for the texture asset to be imported by the AssetManager and returning the result (new texture path on success of an empty string on failure)
	if (AssetManager::importAssetSync(new_texture_path))
	{
		result = std::move(new_texture_path);
	}

	return result;
}
// Method importing an asset in the background (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::import_new_asset_asynchronously()
{
	// making a base target path for the asset and generating a unique virtual path for it
	const StringStack<> &base_asset_path = String::joinPaths(DIRECTORY_FOR_NEW_ASSETS,
		String::basename(TEXTURE_PATH));
	const String &new_texture_path = AssetManager::generateUniquePath(base_asset_path);
	if (!create_asset(TEXTURE_PATH, new_texture_path))
	{
		return;
	}

	// launching the importing process in the background (asynchronously, without blocking the main thread) and displaying a notification, if it has started successfully
	// to catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method (see method "process_end()" here)
	if (AssetManager::importAssetAsync(new_texture_path))
	{
		success_notification(String::format("Asset import process has started successfully: \"%s\"",
			new_texture_path.get()));
	}
	else
	{
		error_notification(
			String::format("Can't start asset import process: \"%s\"", new_texture_path.get()));
	}
}
// Method removing an asset in the background (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::remove_asset_asynchronously()
{
	// checking whether there are any assets in the source directory to remove (if not, report and return)
	const Vector<String> &asset_paths = AssetManager::getAssetPathsForDirectory(
		DIRECTORY_FOR_NEW_ASSETS);
	if (asset_paths.empty())
	{
		error_notification(String::format("There are no assets to remove: \"%s\"\n",
			DIRECTORY_FOR_NEW_ASSETS));
		return;
	}

	// launching the asset removal process in the background (asynchronously, without blocking the main thread) and displaying a notification, if it has started successfully
	// to catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method (see method "process_end()" here)
	const String &asset_path_to_remove = asset_paths.last();
	if (AssetManager::removeAssetAsync(asset_path_to_remove))
	{
		success_notification(String::format("Asset removal process has started successfully: \"%s\"",
			asset_path_to_remove.get()));
	}
	else
	{
		error_notification(
			String::format("Can't start asset removal process: \"%s\"", asset_path_to_remove.get()));
	}
}
// Method moving an asset to a new location in the background (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::move_asset_asynchronously()
{
	// checking whether there are any assets in the source directory to move (if not, report and return)
	const Vector<String> &asset_paths = AssetManager::getAssetPathsForDirectory(
		DIRECTORY_FOR_NEW_ASSETS);
	if (asset_paths.empty())
	{
		error_notification(String::format("There are no assets to move: \"%s\"\n",
			DIRECTORY_FOR_NEW_ASSETS));
		return;
	}

	// trying to create a destination directory and displaying error notification in case of failure
	if (!AssetManager::createDirectory(DIRECTORY_FOR_MOVED_ASSETS))
	{
		error_notification(
			String::format("Can't create directory: \"%s\"", DIRECTORY_FOR_MOVED_ASSETS));
		return;
	}

	// making a new asset path and generating a unique virtual path for it
	const String &asset_path_to_move = asset_paths.last();
	const StringStack<> &base_asset_path = String::joinPaths(DIRECTORY_FOR_MOVED_ASSETS,
		asset_path_to_move.basename());
	const String &new_asset_path = AssetManager::generateUniquePath(base_asset_path);

	// launching the "move asset" process in the background (asynchronously, without blocking the main thread) and displaying a notification, if it has started successfully
	// to catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method (see method "process_end()" here)
	if (AssetManager::moveAssetAsync(asset_path_to_move, new_asset_path))
	{
		success_notification(
			String::format("Asset moving process has started successfully: from \"%s\" to \"%s\"",
				asset_path_to_move.get(), new_asset_path.get()));
	}
	else
	{
		error_notification(String::format("Can't start asset moving process: from \"%s\" to \"%s\"",
			asset_path_to_move.get(), new_asset_path.get()));
	}
}
// Method renaming an asset asynchronously (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::rename_asset_asynchronously()
{
	// getting paths for all assets in the directory and checking whether it is empty (if so, report and return)
	const Vector<String> &asset_paths = AssetManager::getAssetPathsForDirectory(
		DIRECTORY_FOR_NEW_ASSETS);
	if (asset_paths.empty())
	{
		error_notification(String::format("There are no assets to rename: \"%s\"\n",
			DIRECTORY_FOR_NEW_ASSETS));
		return;
	}

	// as an example, we rename the last file
	const String &asset_path_to_rename = asset_paths.last();

	const StringStack<> &new_asset_dir_path = String::dirname(asset_path_to_rename);
	const char *NEW_ASSET_FILE_NAME = "Renamed Asset";

	// making a new asset path and generating a unique virtual path for it
	StringStack<> new_asset_path = String::joinPaths(new_asset_dir_path, NEW_ASSET_FILE_NAME);
	new_asset_path += '.' + String::extension(asset_path_to_rename);

	new_asset_path = AssetManager::generateUniquePath(new_asset_path);

	// getting a base name for it to set while renaming
	const StringStack<> &new_asset_name = new_asset_path.basename();

	// putting a new "asset rename" task to a queue and displaying the corresponding notification depending on the result
	if (AssetManager::renameAssetAsync(asset_path_to_rename, new_asset_name))
	{
		success_notification(
			String::format("Asset renaming process has started successfully: from \"%s\" to \"%s\"",
				asset_path_to_rename.get(), new_asset_path.get()));
	}
	else
	{
		error_notification(String::format("Can't start asset renaming process: from \"%s\" to \"%s\"",
			asset_path_to_rename.get(), new_asset_path.get()));
	}
}
// Method copying an asset asynchronously (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::copy_asset_asynchronously()
{
	// getting paths for all assets in the directory and checking whether it is empty (if so, report and return)
	const Vector<String> &asset_paths = AssetManager::getAssetPathsForDirectory(
		DIRECTORY_FOR_NEW_ASSETS);
	if (asset_paths.empty())
	{
		error_notification(String::format("There are no assets to copy: \"%s\"\n",
			DIRECTORY_FOR_NEW_ASSETS));
		return;
	}

	// as an example, we copy the last file
	const String &asset_path_to_copy = asset_paths.last();

	// creating a new directory
	if (!AssetManager::createDirectory(DIRECTORY_FOR_COPIED_ASSETS))
	{
		error_notification(
			String::format("Can't create directory: \"%s\"", DIRECTORY_FOR_COPIED_ASSETS));
		return;
	}

	// making a base path and generating a unique virtual path for a new copy of the asset
	const StringStack<> &base_asset_path = String::joinPaths(DIRECTORY_FOR_COPIED_ASSETS,
		asset_path_to_copy.basename());
	const String &new_asset_path = AssetManager::generateUniquePath(base_asset_path);

	// putting a new "asset copy" task to a queue and displaying the corresponding notification depending on the result
	if (AssetManager::copyAssetAsync(asset_path_to_copy, new_asset_path))
	{
		success_notification(
			String::format("Asset copying process has started successfully: from \"%s\" to \"%s\"",
				asset_path_to_copy.get(), new_asset_path.get()));
	}
	else
	{
		error_notification(String::format("Can't start asset copying process: from \"%s\" to \"%s\"",
			asset_path_to_copy.get(), new_asset_path.get()));
	}
}
// Method printing out the list of paths and GUIDs for all assets in the root project directory (including all subdirectories)
void AssetsPlugin::print_all_known_assets()
{
	Log::message("[Assets Plugin] All asset paths:\n");
	for (const String &path : AssetManager::getAssetPaths())
	{
		Log::message("\t%s\n", path.get());
	}
	Log::message("\n[Assets Plugin] All asset GUIDs:\n");
	for (const UGUID &guid : AssetManager::getAssetGUIDs())
	{
		Log::message("\t%s\n", guid.getFileSystemString());
	}
	Log::message("\n");
}
// Method printing out general information for the specified asset path
void AssetsPlugin::print_asset_information(const char *asset_path)
{
	const UGUID &asset_guid = AssetManager::getAssetGUIDFromPath(asset_path);
	// It's only to demonstrate the usage of some API functions:
	Log::message("[Assets Plugin] Asset information:\n");
	Log::message("\tVirtual path: %s\n", AssetManager::getAssetPathFromGUID(asset_guid).get());
	Log::message("\tFile GUID: %s\n", asset_guid.getFileSystemString());
	Log::message("\tAccess: %s\n",
		AssetManager::isAssetWritable(asset_path) ? "read & write" : "read-only");
	auto get_import_parameters_as_string = [asset_path] {
		const CollectionPtr &parameters = AssetManager::getAssetImportParameters(asset_path);
		return convert_vector_to_flat_string(parameters->getNames());
	};
	Log::message("\tImport parameter names: %s\n", get_import_parameters_as_string().get());
 
	// getting the list of GUIDs for all runtimes generated for the asset path and displaying general information about them (if any)
	const Vector<UGUID> &runtime_guids = AssetManager::getRuntimeGUIDs(asset_path);
	if (runtime_guids.empty())
	{
		Log::message("\tNo runtimes\n");
	}
	else
	{
		Log::message("\tRuntimes:\n");
		for (int i = 0, count = runtime_guids.size(); i < count; ++i)
		{
			const UGUID &rt_guid = runtime_guids[i];
			Log::message("\t\tRuntime #%d is %s: Alias (%s) File GUID (%s)\n", i,
				AssetManager::isRuntimePrimary(rt_guid) ? "primary" : "non-primary",
				AssetManager::getRuntimeAlias(rt_guid).get(), rt_guid.getFileSystemString());
		}
	}
}
// Method creating a new directory
void AssetsPlugin::create_directory()
{
	// making a base path for a new directory and generating a unique virtual path for it
	const StringStack<> &base_path = String::joinPaths(DIRECTORY_FOR_NEW_DIRECTORIES,
		"New Directory");
	const String &new_path = AssetManager::generateUniquePath(base_path);

	// creating a new directory for the specified path and displaying the corresponding notification
	if (AssetManager::createDirectory(new_path))
	{
		success_notification(
			String::format("Directory has been created successfully: \"%s\"", new_path.get()));
		assert(AssetManager::isDirectoryWritable(new_path));
	}
	else
	{
		error_notification(String::format("Can't create directory: \"%s\"", new_path.get()));
	}
}
// Method removing a directory with assets (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::remove_directory_with_copied_assets_asynchronously()
{
	// checking if the specified directory exists (if not, report and return)
	if (!AssetManager::isDirectory(DIRECTORY_FOR_COPIED_ASSETS))
	{
		error_notification(
			String::format("Directory doesn't exist: \"%s\"", DIRECTORY_FOR_COPIED_ASSETS));
		return;
	}

	// putting a new "remove directory" task to a queue and displaying the corresponding notification depending on the result
	if (AssetManager::removeDirectoryAsync(DIRECTORY_FOR_COPIED_ASSETS))
	{
		success_notification(String::format("Directory removal process has started successfully: \"%s\"",
			DIRECTORY_FOR_COPIED_ASSETS));
	}
	else
	{
		error_notification(
			String::format("Can't start directory removal process: \"%s\"", DIRECTORY_FOR_COPIED_ASSETS));
	}
}
// Method moving a directory with assets to a new location (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::move_directory_with_copied_assets_asynchronously()
{
	// checking if the source directory exists (if not, report and return)
	if (!AssetManager::isDirectory(DIRECTORY_FOR_COPIED_ASSETS))
	{
		error_notification(
			String::format("Directory doesn't exist: \"%s\"", DIRECTORY_FOR_COPIED_ASSETS));
		return;
	}

	// making a base destination path and generating a unique virtual path for it
	const StringStack<> &base_path = String::joinPaths(DIRECTORY_FOR_NEW_DIRECTORIES,
		"New Directory");
	const String &new_path = AssetManager::generateUniquePath(base_path);

	// putting a new "move directory" task to a queue and displaying the corresponding notification depending on the result
	if (AssetManager::moveDirectoryAsync(DIRECTORY_FOR_COPIED_ASSETS, new_path))
	{
		success_notification(
			String::format("Directory moving process has started successfully: from \"%s\" to \"%s\"",
				DIRECTORY_FOR_COPIED_ASSETS, new_path.get()));
	}
	else
	{
		error_notification(String::format("Can't start directory moving process:  from \"%s\" to \"%s\"",
			DIRECTORY_FOR_COPIED_ASSETS, new_path.get()));
	}
}
// Method renaming a directory with assets (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::rename_directory_with_copied_assets_asynchronously()
{
	// checking if the specified directory exists (if not, report and return)
	if (!AssetManager::isDirectory(DIRECTORY_FOR_COPIED_ASSETS))
	{
		error_notification(
			String::format("Directory doesn't exist: \"%s\"", DIRECTORY_FOR_COPIED_ASSETS));
		return;
	}

	// generating a unique virtual path for the destination directory and getting its base name to change it
	const String &new_path = AssetManager::generateUniquePath(DIRECTORY_FOR_COPIED_ASSETS);
	const StringStack<> &new_name = new_path.basename();

	// putting a new "rename directory" task to a queue and displaying the corresponding notification depending on the result
	if (AssetManager::renameDirectoryAsync(DIRECTORY_FOR_COPIED_ASSETS, new_name))
	{
		success_notification(
			String::format("Directory renaming process has started successfully: from \"%s\" to \"%s\"",
				DIRECTORY_FOR_COPIED_ASSETS, new_path.get()));
	}
	else
	{
		error_notification(String::format("Can't start directory renaming process: from \"%s\" to \"%s\"",
			DIRECTORY_FOR_COPIED_ASSETS, new_path.get()));
	}
}
// Method copying a directory with assets to the specified path (non-blocking, returns immediately without waiting for operation completion)
void AssetsPlugin::copy_directory_with_copied_assets_asynchronously()
{
	// checking if the specified directory exists (if not, report and return)
	if (!AssetManager::isDirectory(DIRECTORY_FOR_COPIED_ASSETS))
	{
		error_notification(
			String::format("Directory doesn't exist: \"%s\"", DIRECTORY_FOR_COPIED_ASSETS));
		return;
	}

	// making the destination base path, to which the directory is to be copied, and generating a new unique virtual path for it
	const StringStack<> &base_path = String::joinPaths(DIRECTORY_FOR_NEW_DIRECTORIES,
		"New Directory");
	const String &new_path = AssetManager::generateUniquePath(base_path);

	// launching the copying process in the background (asynchronously, without blocking the main thread) and displaying a notification, if it has started successfully
	// to catch completion of the operation set a callback via AssetManager::addProcessEndCallback() method (see method "process_end()" here)
	if (AssetManager::copyDirectoryAsync(DIRECTORY_FOR_COPIED_ASSETS, new_path))
	{
		success_notification(
			String::format("Directory copying process has started successfully: from \"%s\" to \"%s\"",
				DIRECTORY_FOR_COPIED_ASSETS, new_path.get()));
	}
	else
	{
		error_notification(String::format("Can't start directory copying process: from \"%s\" to \"%s\"",
			DIRECTORY_FOR_COPIED_ASSETS, new_path.get()));
	}
}
// Method listing all directories known to the AssetManager
void AssetsPlugin::print_all_known_directories()
{
	Log::message("\n[Assets Plugin] All directory paths:\n");
	for (const String &path : AssetManager::getDirectoryPathsAll())
	{
		Log::message("\t%s\n", path.get());
	}
	Log::message("\n");
}
// Method listing all subdirectories of the specified path, that are known to the AssetManager
void AssetsPlugin::print_directories_from_test_directory()
{
	Log::message("\n[Assets Plugin] Directory paths from \"%s\":\n", DIRECTORY_FOR_NEW_DIRECTORIES);
	for (const String &path : AssetManager::getDirectoryPaths(DIRECTORY_FOR_NEW_DIRECTORIES))
	{
		Log::message("\t%s\n", path.get());
	}
	Log::message("\n");
}
// Method creating a new mount point
void AssetsPlugin::create_mount_point()
{
	// checking if a path for a mount point to be created already exists and displaying the corresponding notifications
	if (AssetManager::isExist(DIRECTORY_FOR_MOUNT_POINT))
	{
		if (AssetManager::isAsset(DIRECTORY_FOR_MOUNT_POINT))
		{
			error_notification(
				String::format("Target path already contains a known asset: \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
		}
		else if (AssetManager::isMountPoint(DIRECTORY_FOR_MOUNT_POINT))
		{
			assert(AssetManager::isDirectory(DIRECTORY_FOR_MOUNT_POINT));	// Mount point is a directory
			success_notification(
				String::format("Mount point for the target path already exists: \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
		}
		else if (AssetManager::isDirectory(DIRECTORY_FOR_MOUNT_POINT))
		{
			error_notification(
				String::format("Target path already contains a known directory: \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
		}
		else
		{
			// May be some reserved file or directory already exists there
			error_notification(
				String::format("Target path already exists and may contain a reserved file or some directory: \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
		}
		return;
	}

	// making an absolute path to the mount
	StringStack<> abs_dir_path = String::normalizeDirPath(Engine::get()->getDataPath());
	abs_dir_path = abs_dir_path.trimLast("/");
	abs_dir_path = abs_dir_path.dirname();
	abs_dir_path = String::joinPaths(abs_dir_path, MOUNT_POINT_DIRECTORY_NAME);

	// getting an existing directory for the absolute path or creating it
	if (!Dir::mkdir(abs_dir_path))
	{
		error_notification(String::format("Can't create directory \"%s\"", abs_dir_path.get()));
		return;
	}

	// creating an instance of the MountPointParameters class to specify necessary settings for a new mount point to be created (path and access mode) 
	MountPointParametersPtr mount_params = MountPointParameters::create();
	mount_params->setAbsolutePath(abs_dir_path);
	mount_params->setAccess(MountPointParameters::ACCESS_READWRITE);

	// trying to create a new mount point for the specified path and parameters, displaying a notification depending on the result
	if (AssetManager::createMountPoint(DIRECTORY_FOR_MOUNT_POINT, mount_params))
	{
		success_notification(String::format("Mount point has been created successfully: \"%s\"",
			DIRECTORY_FOR_MOUNT_POINT));
		assert(AssetManager::isMountPoint(DIRECTORY_FOR_MOUNT_POINT));
	}
	else
	{
		error_notification(
			String::format("Can't create mount point: \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
	}
}
// Method removing a mount point
void AssetsPlugin::remove_mount_point()
{
	// checking if the specified mount point exists
	if (!AssetManager::isMountPoint(DIRECTORY_FOR_MOUNT_POINT))
	{
		error_notification(
			String::format("Mount point doesn't exist \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
		return;
	}

	// trying to remove the specified mount point via the AssetManager, displaying a notification depending on the result
	if (AssetManager::removeMountPoint(DIRECTORY_FOR_MOUNT_POINT))
	{
		success_notification(String::format("Mount point has been removed successfully: \"%s\"",
			DIRECTORY_FOR_MOUNT_POINT));
	}
	else
	{
		error_notification(
			String::format("Can't remove mount point: \"%s\"", DIRECTORY_FOR_MOUNT_POINT));
	}
}
// Method printing complete information about the mount point (virtual path, access, and filters)
void AssetsPlugin::print_mount_point_information()
{
	// checking if the specified mount point exists
	if (!AssetManager::isMountPoint(DIRECTORY_FOR_MOUNT_POINT))
	{
		Log::message("[Assets Plugin] Mount point doesn't exist: \"%s\"\n",
			DIRECTORY_FOR_MOUNT_POINT);
		return;
	}

	Log::message("[Assets Plugin] Mount point information\n\n");
	Log::message("\tVirtual path: \"%s\"\n", DIRECTORY_FOR_MOUNT_POINT);

	// retrieving parameters of the specified mount point to a MountPointParameters class instance and print information using its data
	const MountPointParametersPtr mount_params = AssetManager::getMountPointParameters(
		DIRECTORY_FOR_MOUNT_POINT);

	Log::message("\tAbsolute path: \"%s\"\n", mount_params->getAbsolutePath());
	Log::message("\tAccess: \"%s\"\n",
		(mount_params->getAccess() == MountPointParameters::ACCESS_READONLY) ? "read-only"
																			 : "read & write");

	// retrieving exclusive filters of the specified mount point to a vector and printing its contents or a corresponding message in case filters are not set
	const Vector<String> &exclusive_filters = mount_params->getExclusiveFilters();
	if (exclusive_filters.empty())
	{
		Log::message("\tNo exclusive filters\n");
	}
	else
	{
		Log::message("\tExclusive filters: %s\n", convert_vector_to_flat_string(exclusive_filters));
	}

	// retrieving ignore filters of the specified mount point to a vector and printing its contents or a corresponding message in case filters are not set
	const Vector<String> &ignore_filters = mount_params->getIgnoreFilters();
	if (ignore_filters.empty())
	{
		Log::message("\tNo ignore filters\n");
	}
	else
	{
		Log::message("\tIgnore filters: %s\n", convert_vector_to_flat_string(ignore_filters));
	}

	Log::message("\n");
}
// Method reimporting the selected Landscape Map (.lmap) asset
void AssetsPlugin::reimport_selected_landscape_map()
{
	// trying to get an .lmap-file in the current selection
	const String &lmap_path = find_selected_lmap_file();
	if (lmap_path.empty())
	{
		return;
	}

	// getting current import parameters for the .lmap asset to an instance of the Collection class
	CollectionPtr source_import_parameters = AssetManager::getAssetImportParameters(lmap_path);
	LandscapeMapImportParametersPtr lmap_import_parameters = LandscapeMapImportParameters::create();
	lmap_import_parameters->load(source_import_parameters);

	// switching the data filling mode import parameter for the asset
	using DataMode = LandscapeMapImportParameters::DATA_FILLING_MODE;
	DataMode mode = lmap_import_parameters->getDataFillingMode();
	switch (mode)
	{
	case DataMode::DATA_FILLING_MODE_FROM_TILESET: mode = DataMode::DATA_FILLING_MODE_MANUAL; break;
	case DataMode::DATA_FILLING_MODE_MANUAL: mode = DataMode::DATA_FILLING_MODE_FROM_TILESET; break;
	}
	lmap_import_parameters->setDataFillingMode(mode);

	// saving new import parameters and using them to reimport asset asynchronously with corresponding notification on starting the process
	CollectionPtr dest_import_parameters = lmap_import_parameters->save();
	if (AssetManager::reimportAssetAsync(lmap_path, dest_import_parameters))
	{
		success_notification(String::format(
			"Asset reimport process has started successfully: \"%s\"", lmap_path.get()));
	}
	else
	{
		error_notification(
			String::format("Can't start asset reimport process: \"%s\"", lmap_path.get()));
	}
}

// auxiliary method setting the current Editor selection to an asset having the specified path
void AssetsPlugin::select_asset(const char *asset_path)
{
	// checking if an asset with the specified path exists and getting its guid
	assert(AssetManager::isAsset(asset_path));
	const UGUID &asset_guid = AssetManager::getAssetGUIDFromPath(asset_path);
	if (asset_guid.isEmpty())
	{
		error_notification(String::format("Invalid path for asset: \"%s\"", asset_path));
		return;
	}

	// setting the current Editor selection to an asset with the specified guid
	SelectionAction::applySelection(SelectorGUIDs::createRuntimesSelector({asset_guid}));
}

// auxiliary method displaying a success message
void AssetsPlugin::success_notification(const char *message) const
{
	notification(message, Math::vec4_green);
}

// auxiliary method displaying an error message
void AssetsPlugin::error_notification(const char *message) const
{
	notification(message, Math::vec4_red);
}

// auxiliary method displaying a generic notification message using the specified color
void AssetsPlugin::notification(const char *message, const Math::vec4 &color) const
{
	if (label_notification_)
	{
		label_notification_->setFontColor(color);
		label_notification_->setText(message);
	}
	Log::message(color, "[Assets Plugin] %s\n", message);
}

// Auxiliary method creating an asset with the specified destination path for the specified source path
bool AssetsPlugin::create_asset(const char *source_path, const char *dest_path) const
{
	// retrieving a path to destination directory and creating it via the AssetManager (the corresponding callback shall be executed)
	const StringStack<> &dir_path = String::dirname(dest_path);
	if (!AssetManager::createDirectory(dir_path))
	{
		error_notification(String::format("Can't create directory: \"%s\"", dir_path.get()));
		return false;
	}

	// opening the source file for reading data
	FilePtr source_asset = File::create(source_path, "rb");
	if (!source_asset->isOpened())
	{
		error_notification(String::format("Can't open asset for reading: \"%s\"", source_path));
		return false;
	}

	// opening the destination asset file for writing data
	FilePtr dest_asset = File::create(dest_path, "wb");
	if (!dest_asset->isOpened())
	{
		error_notification(String::format("Can't open asset for writing: \"%s\"", dest_path));
		return false;
	}

	// declaring a 32Kb buffer for transferring data 
	const size_t MEMORY_SIZE = 1024 * 32;
	Vector<unsigned char> memory(MEMORY_SIZE);
	// reading and writing data from the source file to the destination asset
	while (0 == source_asset->eof())
	{
		const size_t bytes_read = source_asset->read(&memory[0], memory.size());
		const size_t bytes_written = dest_asset->write(&memory[0], bytes_read);
		if (bytes_read != bytes_written)
		{
			dest_asset->close();
			dest_asset.clear();
			Dir::remove(dest_path);
			error_notification(String::format("Can't write data to asset: \"%s\"", dest_path));
			return false;
		}
	}

	return true;
}

// Auxiliary method iterating over the current selection and finds the path to the first .lmap asset in the current Editor selection.
String AssetsPlugin::find_selected_lmap_file() const
{
	String lmap_path;

	// getting guids of runtimes for all currently selected assets, returning an empty string if nothing is selected
	SelectorGUIDs *selector = Selection::getSelectorRuntimes();
	if (!selector || selector->empty())
	{
		return lmap_path;
	}

	// getting guids of all currently selected assets
	const Vector<UGUID> &selected_guids = selector->guids();
	for (const UGUID &guid : selected_guids)
	{
		// obtaining asset path by its GUID and checking if the path corresponds to a registered asset
		String virtual_path = AssetManager::getAssetPathFromGUID(guid);
		if (!AssetManager::isAsset(virtual_path))
		{
			Log::message("[Assets Plugin] Invalid asset path: %s\n", virtual_path.get());
			continue;
		}

		// getting an extension for the asset file and return the virtual path if it is an .lmap asset
		const StringStack<> &extension = virtual_path.extension().getLower();
		if (!String::equal(extension, "lmap"))
		{
			continue;
		}

		lmap_path = std::move(virtual_path);
		break;
	}

	return lmap_path;
}

// Callback function to be executed on adding a new asset
void AssetsPlugin::asset_added(const char *path)
{
	Log::message("[Assets Plugin] Asset added: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed before removing an asset
void AssetsPlugin::asset_before_remove(const char *path)
{
	Log::message("[Assets Plugin] Asset before removing: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed after removing an asset
void AssetsPlugin::asset_removed(const char *path)
{
	Log::message("[Assets Plugin] Asset removed: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed on changing an asset
void AssetsPlugin::asset_changed(const char *path)
{
	Log::message("[Assets Plugin] Asset changed: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed on moving an asset to a new location
void AssetsPlugin::asset_moved(const char *path_from, const char *path_to)
{
	Log::message("[Assets Plugin] Asset moved from \"%s\" to \"%s\"\n", path_from, path_to);
	// Add your custom handling code here...
}

// Callback function to be executed on adding a new directory
void AssetsPlugin::directory_added(const char *path)
{
	Log::message("[Assets Plugin] Directory added: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed before removing a directory
void AssetsPlugin::directory_before_remove(const char *path)
{
	Log::message("[Assets Plugin] Directory before removing: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed after removing a directory
void AssetsPlugin::directory_removed(const char *path)
{
	Log::message("[Assets Plugin] Directory removed: %s\n", path);
	// Add your custom handling code here...
}

// Callback function to be executed on moving a directory to a new location
void AssetsPlugin::directory_moved(const char *path_from, const char *path_to)
{
	Log::message("[Assets Plugin] Directory moved from \"%s\" to \"%s\"\n", path_from, path_to);
	// Add your custom handling code here...
}

// Callback function to be executed on beginning of asynchronous asset processing by the Asset Manager (e.g., validation, import, reimport, removal, copying, conversion, migration etc.)
void AssetsPlugin::process_begin()
{
	Log::message("[Assets Plugin] Processing started\n");
	// Add your custom handling code here...
}

// Callback function to be executed on completion of asynchronous asset processing by the Asset Manager (e.g., validation, import, reimport, removal, copying, conversion, migration etc.)
void AssetsPlugin::process_end()
{
	Log::message("[Assets Plugin] Processing completed\n");
	// Add your custom handling code here...
}

// Callback function to be executed on changing selection in the Editor
void AssetsPlugin::selection_changed()
{
	// trying to get an .lmap-file in the current selection
	const String &lmap_path = find_selected_lmap_file();

	// updating the button text and status depending on the result
	const char BUTTON_LMAP_REIMPORT_NAME[] = "Reimport Landscape Map";
	const StringStack<> &button_name = String::format("%s%s%s", BUTTON_LMAP_REIMPORT_NAME,
		lmap_path.empty() ? "" : " - ", lmap_path.get());
	button_lmap_reimporting_->setText(button_name);
	button_lmap_reimporting_->setEnabled(lmap_path.size() > 0);
}
最新更新: 2023-06-21
Build: ()