Godot之StringName解析

news2025/1/11 2:52:28

类描述

在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 进行比较的情况。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

时光总是催人老 time

文章目录 时光总是催人老 time语法示例更多信息 时光总是催人老 time … note:: 林花谢了春红&#xff0c;太匆匆。无奈朝来寒雨晚来风。 李煜《相见欢林花谢了春红》 Linux time命令的用途&#xff0c;在于测量指定命令消耗的时间。 最常用的在于大概评估一个程序的运行时…

APP流量变现——4项关键指标决定了APP混合变现的收入

APP流量变现的方式有很多种&#xff0c;主要的可以分为IAA&#xff08;广告&#xff09;收入、IAP&#xff08;用户应用内付费&#xff09;收入、订阅收入、单次买断收入。这里主要围绕当前流行的混合变现模式&#xff0c;即广告收入&#xff08;IAA&#xff09;应用内付费&…

vue3+vite开发生产环境区分

.env.development VITE_APP_TITLE本地.env.production VITE_APP_TITLE生产-ts文件中应用 console.log(import.meta.env.VITE_APP_TITLE)在html中应用&#xff0c;需要安装 html 模板插件 pnpm add vite-plugin-html -Dvite.config.ts中 import { createHtmlPlugin } from v…

鸿蒙原生应用再添新丁!万达 入局鸿蒙

鸿蒙原生应用再添新丁&#xff01;万达 入局鸿蒙 来自 HarmonyOS 微博1月11日消息&#xff0c;#万达酒店及度假村启动鸿蒙原生应用及元服务开发# 作为具有中国特色的国牌服务酒店标杆之一&#xff0c;万达酒店及度假村Wanda 将带来全新的服务和交互方式&#xff0c;一步获取“…

【AI视野·今日Robot 机器人论文速览 第七十三期】Tue, 9 Jan 2024

AI视野今日CS.Robotics 机器人学论文速览 Tue, 9 Jan 2024 Totally 40 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers Digital Twin for Autonomous Surface Vessels for Safe Maritime Navigation Authors Daniel Menges, Andreas Von Brandis, A…

基于ssm运动会管理系统的设计与实现 【附源码】

基于ssm运动会管理系统的设计与实现 【附源码】 &#x1f345; 作者主页 央顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuil…

Flashduty 案例分享 - 途游游戏

Flashduty 作为功能完备的事件OnCall中心&#xff0c;可以接入云上、云下不同监控系统&#xff0c;统一做告警降噪分派、认领升级、排班协同&#xff0c;已经得到众多先进企业的认可。我们采访了一些典型客户代表&#xff0c;了解他们的痛点、选型考虑和未来展望&#xff0c;集…

【排序算法】四、堆排序(C/C++)

「前言」文章内容是排序算法之堆排序的讲解。&#xff08;所有文章已经分类好&#xff0c;放心食用&#xff09; 「归属专栏」排序算法 「主页链接」个人主页 「笔者」枫叶先生(fy) 目录 堆排序1.1 原理1.2 堆的向下调整1.3 堆排序代码实现1.3 性质总结 堆排序 1.1 原理 概念介…

爬取去哪网旅游攻略信息

代码展现&#xff1a; import requests import parsel import csv import time f open(旅游去哪攻略.csv,modea,encodingutf-8,newline) csv_writer csv.writer(f) csv_writer.writerow([标题,浏览量,日期,天数,人物,人均价格,玩法]) for page in range(1,5):url fhttps://…

JS 函数

函数就是封装了一段可以被重复执行调用的代码块。目的&#xff1a;让大量代码重复利用 1、声明函数 方式一&#xff1a;利用函数关键字自定义函数&#xff08;命名函数&#xff09; function 函数名&#xff08;&#xff09;{//函数体代码} function是声明函数的关键字&#…

气膜建筑的消防安全问题如何保障?

气膜建筑作为一种独特的建筑形式&#xff0c;拥有广泛的应用领域。然而&#xff0c;由于其密闭性特点&#xff0c;人们更加关注其消防安全问题。以下是保障气膜建筑消防安全的几个关键措施&#xff1a; 采用难燃材料&#xff1a; 气膜建筑所使用的建筑膜材采用B1级难燃材料&…

Go后端开发 -- 条件、循环语句 defer语句

Go后端开发 – 条件、循环语句 && defer语句 文章目录 Go后端开发 -- 条件、循环语句 && defer语句一、条件语句1.if ... else 语句2.switch语句3.select语句 二、循环语句1.for循环 三、defer语句1.defer语句的作用2.defer和return的先后顺序3.recover错误拦截…

Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent

文章目录 Pre概述Code源码分析 Pre Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent 概述 Spring Boot 的广播机制是基于观察者模式实现的&#xff0c;它允许在 Spring 应用程序中发布和监听事件。这种机制的主要目的是为了实现解耦&#…

【JaveWeb教程】(19) MySQL数据库开发之 MySQL数据库操作-DML 详细代码示例讲解

目录 3. 数据库操作-DML3.1 增加(insert)3.2 修改(update)3.3 删除(delete)3.4 总结 3. 数据库操作-DML DML英文全称是Data Manipulation Language(数据操作语言)&#xff0c;用来对数据库中表的数据记录进行增、删、改操作。 添加数据&#xff08;INSERT&#xff09;修改数据…

第二百六十回

文章目录 知识回顾示例代码经验总结 我们在上一章回中介绍了通道相关的内容&#xff0c;本章回中将介绍其中的一种通道&#xff1a;MethodChannnel.闲话休提&#xff0c;让我们一起Talk Flutter吧。 知识回顾 我们在上一章回中介绍了通道的概念和作用&#xff0c;并且提到了通…

脱机I/O方式和假脱机系统

提示&#xff1a;在写这个博客的时候小编更加的觉得计算机基础知识的重要性了&#xff0c;而且对计算机的整个发展历程和计算机的底层工作原理特别感兴趣 脱机I/O方式和假脱机系统 一、脱机I/O方式二、假脱机系统1、假脱机技术&#xff08;SPOOLing&#xff0c; simulataneaus …

一日难再晨及时当勉励 date

文章目录 Linux shell 获取更改系统时间默认输入显示时区世界协调时格式化日期更多信息 Linux shell 获取更改系统时间 … note:: 时光只解催人老&#xff0c;不信多情&#xff0c;长恨离亭&#xff0c;泪滴春衫酒易醒。 - 晏殊《采桑子时光只解催人老》date命令可以用来打印…

GPT 商店强势来袭,人人都要有自己的 GPTs

作者&#xff1a;苍何&#xff0c;前大厂高级 Java 工程师&#xff0c;阿里云专家博主&#xff0c;CSDN 2023 年 实力新星&#xff0c;土木转码&#xff0c;现任部门技术 leader&#xff0c;专注于互联网技术分享&#xff0c;职场经验分享。 &#x1f525;热门文章推荐&#xf…

Python学习从0到1 day1 你好 Python

我会在那腥臭腐朽的日子里熠熠生辉 ——24.1.11 1.第一个Python程序 安装python程序,输出第一个程序:你好,世界 print("Hello World"); 2.Python解释器 python解释器,是一个计算机程序,用来翻译python代码,并提交给计算机执行 功能:1.翻译代码 2.提交给计算机…

快速打通 Vue 3(四):标签的 ref 属性与 Vue3 生命周期

很激动进入了 Vue 3 的学习&#xff0c;作为一个已经上线了三年多的框架&#xff0c;很多项目都开始使用 Vue 3 来编写了 这一组文章主要聚焦于 Vue 3 的新技术和新特性 如果想要学习基础的 Vue 语法可以看我专栏中的其他博客 Vue&#xff08;一&#xff09;&#xff1a;Vue 入…