Godot之StringName解析

类描述

在Godot中,StringName是唯一字符串的内置类型。

StringName 是不可变的字符串,用于唯一名称的通用表示(也叫“字符串内嵌”)。值相同的两个 StringName 是同一个对象。进行比较时比普通 String 要快很多。

对于需要 StringName 的方法,你通常可以只传 String,会自动进行转换,不过有时候你可能会想要提前使用 StringName 构造函数来构造 StringName,在 GDScript 中也可以用 &"example" 语法。

Godot中的NodePath,这是与此类似的概念,针对存储预解析的场景树路径设计,NodePath我们后面会进行解析。

String 的所有方法都在这个类中可用。它们会将 StringName 转换为字符串,返回的也是字符串。这样做效率非常低,应该只在需要字符串时使用。

注意:转换为布尔值时,空的 StringName(StringName(""))为 false,其他 StringName 均为 true。不能使用 not 运算符。请改用 is_empty 来检查空的 StringName。

核心思想概述

StringName内部实现了一个静态哈希表_table,将所有值相同的字符串,在_table中存储唯一一份,并对字符串值实现了引用计数,创建时+1,销毁时-1,为0时,从_table中移除对应节点,并释放字符串占用的内存。

关键代码

关键成员变量

_Data

_Data 是一个用于存储字符串的双端链表,支持引用计数、C string和String两种格式字符串。其他见代码注释。

	// 一个存储字符串的双端链表
	struct _Data {
		SafeRefCount refcount;
		// 静态引用次数
		SafeNumeric<uint32_t> static_count;
		// 所存储的字符串,可以以const char *和String两种形式存在
		const char *cname = nullptr;
		String name;
#ifdef DEBUG_ENABLED
		uint32_t debug_references = 0;
#endif
		String get_name() const { return cname ? String(cname) : name; }
		// 记录在_table中的索引
		int idx = 0;
		// 存储字符串的哈希值
		uint32_t hash = 0;
		// 前向节点指针
		_Data *prev = nullptr;
		// 后向节点指针
		_Data *next = nullptr;
		_Data() {}
	};

	// 在StringName变量中,只关心_data指向的节点,在静态_table中,才关心它的前向和后向节点
	_Data *_data = nullptr;

_table

_table定义为静态的目的,就是唯一且被所有StringName共享,从而实现在字符串相等时,可以共享一个字符串

enum {
	STRING_TABLE_BITS = 16,
	STRING_TABLE_LEN = 1 << STRING_TABLE_BITS,		// 静态_table数组大小
    STRING_TABLE_MASK = STRING_TABLE_LEN - 1		// 静态_table数组索引的掩码
};

// 静态_table,即被所有StringName共享,从而实现在字符串相等时,可以共享一个字符串
static _Data *_table[STRING_TABLE_LEN];

静态哈希表_table的原理图如下所示,也是StringName的精髓所在。

关键成员函数

构造函数

StringName::StringName(const String &p_name, bool p_static) {
	_data = nullptr;

	ERR_FAIL_COND(!configured);

	if (p_name.is_empty()) {
		return;
	}

	MutexLock lock(mutex);

	// 计算字符串的哈希值
	uint32_t hash = p_name.hash();
	// 计算在静态_table中的索引
	uint32_t idx = hash & STRING_TABLE_MASK;

	_data = _table[idx];

	while (_data) {
		// 相等的条件:哈希值相等 and 字符串相等
		if (_data->hash == hash && _data->get_name() == p_name) {
			break;
		}
		_data = _data->next;
	}

	// 如果找到,引用计数+1
	if (_data && _data->refcount.ref()) {
		// exists
		if (p_static) {
			_data->static_count.increment();
		}
#ifdef DEBUG_ENABLED
		if (unlikely(debug_stringname)) {
			_data->debug_references++;
		}
#endif
		return;
	}

	// 如果在静态_table中没有找到,则创建一个新的,并添加到_table
	_data = memnew(_Data);
	_data->name = p_name;
	_data->refcount.init();
	_data->static_count.set(p_static ? 1 : 0);
	_data->hash = hash;
	_data->idx = idx;
	_data->cname = nullptr;
	_data->next = _table[idx];
	_data->prev = nullptr;
#ifdef DEBUG_ENABLED
	if (unlikely(debug_stringname)) {
		// Keep in memory, force static.
		_data->refcount.ref();
		_data->static_count.increment();
	}
#endif

	if (_table[idx]) {
		_table[idx]->prev = _data;
	}
	_table[idx] = _data;
}

析构函数

void StringName::unref() {
	ERR_FAIL_COND(!configured);

	// _data有效 且 unref后,引用为0,才会进行释放
	if (_data && _data->refcount.unref()) {
		MutexLock lock(mutex);

		if (CoreGlobals::leak_reporting_enabled && _data->static_count.get() > 0) {
			if (_data->cname) {
				ERR_PRINT("BUG: Unreferenced static string to 0: " + String(_data->cname));
			} else {
				ERR_PRINT("BUG: Unreferenced static string to 0: " + String(_data->name));
			}
		}

		// 删除双向链表中的节点
		if (_data->prev) {
			_data->prev->next = _data->next;
		} else {
			if (_table[_data->idx] != _data) {
				ERR_PRINT("BUG!");
			}
			_table[_data->idx] = _data->next;
		}

		if (_data->next) {
			_data->next->prev = _data->prev;
		}
		// 释放内存
		memdelete(_data);
	}

	_data = nullptr;
}

	// 析构函数
	_FORCE_INLINE_ ~StringName() {
		if (likely(configured) && _data) { //only free if configured
			unref();
		}
	}

查找函数

StringName StringName::search(const char *p_name) {
	ERR_FAIL_COND_V(!configured, StringName());
	// 判断指针非空
	ERR_FAIL_NULL_V(p_name, StringName());
	// 字符串不以'\0'开头
	if (!p_name[0]) {
		return StringName();
	}

	MutexLock lock(mutex);

	//计算哈希值,进而计算在静态_table中的索引
	uint32_t hash = String::hash(p_name);
	uint32_t idx = hash & STRING_TABLE_MASK;

	_Data *_data = _table[idx];

	// 检索在链表中的结点
	while (_data) {
		// compare hash first
		if (_data->hash == hash && _data->get_name() == p_name) {
			break;
		}
		_data = _data->next;
	}

	// 节点有效 且 引用计数+1
	if (_data && _data->refcount.ref()) {
#ifdef DEBUG_ENABLED
		if (unlikely(debug_stringname)) {
			_data->debug_references++;
		}
#endif
		// 返回
		return StringName(_data);
	}

	return StringName(); //does not exist
}

赋值函数

// 赋值
void StringName::operator=(const StringName &p_name) {
	if (this == &p_name) {
		return;
	}

	// 赋新值,需先减少以前的字符的引用串数
	unref();

	// 引用计数+1
	if (p_name._data && p_name._data->refcount.ref()) {
		_data = p_name._data;
	}
}

一个重要的宏

SNAME(m_arg)用于优化 StringName(字符串名称)对象的创建。在许多编程场景中,频繁地创建和销毁同一字符串名称可能会对性能产生影响,特别是在高性能要求的场合。SNAME 宏通过内部的静态局部变量实现了一种高效的缓存机制,在首次使用时创建并存储特定字符串名称,后续调用时直接返回已创建的实例。

#define SNAME(m_arg) ([]() -> const StringName & { static StringName sname = _scs_create(m_arg, true); return sname; })()

SNAME 宏旨在提升高频率创建特定字符串名称场景下的性能,但在大多数情况下并不推荐滥用,仅在确实需要提高性能的关键路径上使用。

推荐在以下场景使用:

  • 在 Control::get_theme_() 和 Window::get_theme_() 等高频主题方法中;
  • 在 emit_signal(,..) 和 call_deferred(,..) 等信号关联的方法中;
  • 在重写 _set 和 _get 方法时与 StringName 进行比较的情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/316320.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【密码学】python密码学库pycryptodome

记录了一本几乎是10年前的书&#xff08;python绝技–用python成为顶级黑客&#xff09;中过时的内容 p20 UNIX口令破解机 里面提到了python标准库中自带的crypt库&#xff0c;经验证Python 3.12.1中并没有这个自带的库&#xff0c;密码学相关的库目前&#xff08;2024.1.12&a…

QT周四作业

题目&#xff1a; 代码&#xff1a; widget.cpp #include "widget.h" #include "ui_widget.h" #include <QDebug> Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);ui->lineEditName->setPlac…

JAVA数组以及小练习

目录 数组的概述和静态初始化 数组的地址值和元素访问 数组的遍历 数组的动态初始化 数组练习 数组的概述和静态初始化 package 数组;public class array1 {public static void main(String[] args){//格式//静态初始化//数据类型 [] 数组名 new 数组类型[]{元素1&#xf…

[开发语言][c++][python]:C++与Python中的赋值、浅拷贝与深拷贝

C与Python中的赋值、浅拷贝与深拷贝 1. Python中的赋值、浅拷贝、深拷贝2. C中的赋值、浅拷贝、深拷贝2.1 概念2.2 示例&#xff1a;从例子中理解1) 不可变对象的赋值、深拷贝、浅拷贝2) 可变对象的赋值、浅拷贝与深拷贝3) **可变对象深浅拷贝(外层、内层改变元素)** 写在前面&…

将WAP网站封装成App体验的全新策略

一、传统的App封装方式 传统的App封装技术通常依赖于WebView组件&#xff0c;将WAP内容嵌入到一个原生App框架中。这种方法虽然可以快速实现WAP到App的转换&#xff0c;但存在着明显的缺陷&#xff1a;首先&#xff0c;WebView的性能和用户体验都无法与原生组件相提并论&#x…

2024年1月12日:清爽无糖rio留下唇齿之间的香甜

友利奈绪的时间管理 2024年1月12日08:02:28进行java程序设计的上课准备 2024年1月12日08:02:44知道java的题目有18道 2024年1月12日08:43:07随机数去重比较 2024年1月12日08:54:03C语言题目最小公倍数 2024年1月12日08:58:37C语言题目二维数组变一维数组 2024年1月12日10…

四种无监督聚类算法说明

目录 一、K-Means无监督学习&#xff08;K-Means&#xff09;的认识-CSDN博客​​​​​​ 二、Mini-Batch K-Means -- Centroid models 三、AffinityPropagation (Hierarchical) -- Connectivity models 四、Mean Shift -- Centroid models 无监督聚类是一种机器学习技术&…

Star 8K+,使用.NET开发的开源NoSQL数据库

LiteDB 是一个轻量级、快速、易用的 .NET NoSQL 嵌入式数据库&#xff0c;完全用 C# 托管代码开发&#xff0c;并且是免费和开源的。它非常适合在移动应用&#xff08;Xamarin iOS/Android&#xff09;和小型的桌面/Web 应用中使用。 主要特点 简单易用的 API&#xff0c;类似…

软件项目质量保证措施-word

一、 质量保障措施 二、 项目质量管理保障措施 &#xff08;一&#xff09; 资深的质量经理与质保组 &#xff08;二&#xff09; 全程参与的质量经理 &#xff08;三&#xff09; 合理的质量控制流程 1&#xff0e; 质量管理规范&#xff1a; 2&#xff0e; 加强协调管理&…

BikeDNA(八)外在分析:OSM 与参考数据的比较2

BikeDNA&#xff08;八&#xff09;外在分析&#xff1a;OSM 与参考数据的比较2 1.数据完整性 见链接 2.网络拓扑结构 见链接 3.网络组件 本节仔细研究两个数据集的网络组件特征。 断开连接的组件不共享任何元素&#xff08;节点/边&#xff09;。 换句话说&#xff0c;…

MES生产执行系统在生产车间的主要作用

MES生产执行系统提供从生产订单下达到产品完成全流程的优化管理。实现现场设备、执行系统及管理系统的集成&#xff0c;实时监控生产管理各项绩效指标。 如果说ERP是上层决策&#xff0c;生产车间是下层执行&#xff0c;那么MES就是连接管理软件和一线生产的中间桥梁。 MES也…

c++静态数据成员

目录 静态成员变量 1. 问&#xff1a;这是为什么呢&#xff1f; (以下结束均为个人理解&#xff0c;如有问题&#xff0c;请指教) 2. 使用场景(举一个例子) 代码中需要注意的点&#xff1a; 3.总结&#xff1a; 静态成员函数 使用场景&#xff1a; 静态成员函数中没有…

大模型核心技术原理: Transformer架构详解

在大模型发展历程中&#xff0c;有两个比较重要点&#xff1a;第一&#xff0c;Transformer 架构。它是模型的底座&#xff0c;但 Transformer 不等于大模型&#xff0c;但大模型的架构可以基于 Transformer&#xff1b;第二&#xff0c;GPT。严格意义上讲&#xff0c;GPT 可能…

Linux 内核如何根据设备树文件来匹配内核

一. 简介 上一篇文章学习了 Linux内核如何确定是否支持此设备&#xff0c;如果支持&#xff0c;设备就会启动 Linux 内核。 文章地址如下&#xff1a; 设备树根节点下的compatile属性的作用-CSDN博客 本文继上面文章的学习。这里简单看一下&#xff0c; Linux 内核是如何根…

odoo17 | 模型之间的交互

前言 在前一章中&#xff0c;我们使用继承来修改模块的行为。在我们的房地产场景中&#xff0c;我们希望更进一步&#xff0c;能够为我们的客户生成发票。Odoo提供了一个发票&#xff08;Invoicing&#xff09;模块&#xff0c;所以直接从我们的房地产模块创建一个发票会很简洁…

VS报错:error:LNK2005 _main 已经在 *.obj 中定义

应该是重定义了&#xff0c;但是又解决不了&#xff0c;看似又没有重定义啊&#xff0c;就在一个文件定义了啊&#xff1f;怎么会出现这种情况呢&#xff1f;关键是&#xff0c;编译报错&#xff0c;程序运行不了了。 这里提一下我的前期操作&#xff0c;是因为将一个头文件和…

图像监视:在 Visual Studio 调试器中查看内存中图像

先决条件 本教程假定您具有以下可用项&#xff1a; 安装了 Update 1 的 Visual Studio 2012 Professional&#xff08;或更高版本&#xff09;。更新 1 可在此处下载。在 Windows 计算机上安装 OpenCV&#xff08;教程&#xff1a;在 Windows 中安装&#xff09;。能够在 Visua…

【Spring 篇】深入探索:Spring集成Web环境的奇妙世界

嗨&#xff0c;亲爱的小白们&#xff01;欢迎来到这篇有关Spring集成Web环境的博客。如果你曾对如何在Spring中构建强大的Web应用程序感到好奇&#xff0c;那么这里将为你揭示Web开发的神秘面纱。我们将用情感丰富、语句通顺的文字&#xff0c;以小白友好的方式&#xff0c;一探…

survey和surveyCV:如何用R语言进行复杂抽样设计、权重计算和10折交叉验证?

一、引言 在实际调查和研究中&#xff0c;我们往往面临着样本选择的复杂性。复杂抽样设计能够更好地反映真实情况&#xff0c;提高数据的代表性和可靠性。例如&#xff0c;多阶段抽样可以有效地解决大规模调查的问题&#xff0c;整群抽样能够在保证样本的随机性的同时减少资源消…

D25XB100-ASEMI家用电器整流桥D25XB100

编辑&#xff1a;ll D25XB100-ASEMI家用电器整流桥D25XB100 型号&#xff1a;D25XB100 品牌&#xff1a;ASEMI 封装&#xff1a;GBJ-5&#xff08;带康铜丝&#xff09; 平均正向整流电流&#xff08;Id&#xff09;&#xff1a;25A 最大反向击穿电压&#xff08;VRM&…