Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supports customizing the display of all data types in Lua on the project side(支持在项目侧自定义Lua中所有类型数据的展示方式) #51

Merged
merged 3 commits into from
Feb 19, 2024

Conversation

zhangjiequan
Copy link
Contributor

@zhangjiequan zhangjiequan commented Feb 18, 2024

概述

支持在项目侧自定义Lua中所有类型数据的展示方式。

应用场景

项目侧需要自定义显示的情景包括但不限于:

  • 字符串:显示字符串的十六进制信息、ASCII码等(详见:增强字符串信息可视化 IntelliJ-EmmyLua#573
  • 数字:项目侧自定义了数字含义(如使用普通整形数字实现定点数),调试时想要查看对应的转换后的数值

使用说明

  1. 支持自定义Lua中所有类型数据的展示方式,可定制的类型包括:nil、boolean、lightuserdata、number、string、table、function、userdata、thread。
  2. 为实现此功能,你需要在代码中注册类型名称,使用 dbg.registerTypeName("typeName") 方法,并在自定义展示逻辑中通过 if typeName == 'typeName' 来匹配对应的类型。

基础测试用例

测试用例展示了如何注册并自定义Lua中“number”和“string”类型的显示方式。
通过 dbg.registerTypeName 方法注册后,我们在 emmyHelper.queryVariableCustom 函数中定义了特定类型数据的展示逻辑。
例如,对于“number”类型,我们不仅显示其数值,还以十六进制格式呈现;对于“string”类型,我们在原始字符串后追加自定义文本“(Custom!)”。
这样,开发者可以清晰地看到每种数据类型的详细信息,并根据项目需求进行个性化展示。

注意,测试用例中的路径package.cpath需要根据实际情况进行调整。

测试用的lua代码

--region OriginalEmmyLuaSettings
package.cpath = package.cpath .. ';C:/Users/YourName/AppData/Roaming/JetBrains/Rider2023.2/plugins/EmmyLua/debugger/emmy/windows/x64/?.dll'
local dbg = require('emmy_core')
dbg.tcpListen('localhost', 9966)
--...
--endregion OriginalEmmyLuaSettings

--region QueryVariableCustom 
dbg.registerTypeName("number")
dbg.registerTypeName("string")

local emmyHelper = rawget(_G, "emmyHelper")
if emmyHelper then
    emmyHelper.queryVariableCustom = function(variable, obj, typeName, depth)
        if typeName == 'number' then
            local objStr = tostring(obj)
            local hexStr = string.format("%016x", obj)
            local finalStr = string.format("%s ([Hex]:%s)", objStr, hexStr)
            variable.value = finalStr
            return true
        elseif typeName == 'string' then
            variable.value = string.format("%s (Custom!)", obj)
            return true
        end
        return false
    end
end
--endregion QueryVariableCustom 

local testInt = 32
local testString = "Hello World"

Rider下调试的效果

Snipaste_2024-02-18_21-36-21

高级测试用例

高级测试用中,先验证了自定义Lua虚拟机环境中轻量用户数据(lightuserdata)与普通用户数据(userdata)的识别和处理,再测试了在EmmyLua调试器环境中对不同Lua数据类型进行自定义显示的功能。

自定义虚拟机

修改Lua虚拟机,加载自定义的Lua虚拟机C扩展,其中包含了用于识别轻量用户数据的is_lightuserdata函数。同时,写入了全局的lightuserdata testLightUserdata。

本例中,修改的是tolua_runtime

//lua_vm.c

static int is_lightuserdata(lua_State *L)
{
  if (lua_islightuserdata(L, 1))
  {
    lua_pushboolean(L, 1);
  }
  else
  {
    lua_pushboolean(L, 0);
  }
  return 1;
}

int luaopen_mylib(lua_State *L)
{
  lua_register(L, "is_lightuserdata", is_lightuserdata);
  return 0;
}

static int pmain (lua_State *L) {
//...
  luaL_openlibs(L);  /* open standard libraries */

  luaopen_mylib(L);

  // 创建一个lightuserdata并推入栈中
  void *p = (void *)malloc(1); // 仅作示例用途
  lua_pushlightuserdata(L, p);

  // 将lightuserdata保存到全局变量中以供Lua脚本使用
  lua_setglobal(L, "testLightUserdata");
//...

测试用的lua代码

对于userdata类型,特别检查了variable.valueType,以区分是轻量用户数据还是普通用户数据,并相应地调整variable.valueTypeName。
注意,这里返回false,让EmmyLuaDebugger继续处理variable的value。

--region OriginalEmmyLuaSettings
package.cpath = package.cpath .. ';C:/Users/YourName/AppData/Roaming/JetBrains/Rider2023.2/plugins/EmmyLua/debugger/emmy/windows/x64/?.dll'
local dbg = require('emmy_core')
dbg.tcpListen('localhost', 9966)
dbg.waitIDE()
dbg.breakHere()
--endregion OriginalEmmyLuaSettings

--region QueryVariableCustom
dbg.registerTypeName("number")
dbg.registerTypeName("string")
dbg.registerTypeName("lightuserdata")
dbg.registerTypeName("userdata")

local emmyHelper = rawget(_G, "emmyHelper")
if emmyHelper then
    emmyHelper.queryVariableCustom = function(variable, obj, typeName, depth)
        if typeName == 'number' then
            local objStr = tostring(obj)
            local hexStr = string.format("%016x", obj)
            local finalStr = string.format("%s ([Hex]:%s)", objStr, hexStr)
            variable.value = finalStr
            return true
        elseif typeName == 'string' then
            variable.value = string.format("%s (Custom!)", obj)
            return true
        elseif typeName == 'userdata' then
            if variable.valueType == 2 then
                variable.valueTypeName = "lightuserdata (Custom!)"
                return false
            else --variable.valueType == 7
                return false
            end
        end
        return false
    end
end
--endregion QueryVariableCustom 

local testInt = 32
local testString = "Hello World"

local testLightUserdata = testLightUserdata
if is_lightuserdata(testLightUserdata) then
    print("It is lightuserdata.")
else
    print("It is not lightuserdata.")
end

local testUserdata = io.open("test.txt", "w+")
testUserdata:close()

控制台输出

Snipaste_2024-02-21_15-24-41

Rider下调试的效果

Snipaste_2024-02-21_15-51-04

@CppCXY
Copy link
Member

CppCXY commented Feb 18, 2024

如果所有类型都会被导入, 那就没有判断的必要了

@zhangjiequan
Copy link
Contributor Author

如果所有类型都会被导入, 那就没有判断的必要了

是的

@@ -371,7 +371,9 @@ void Debugger::GetVariable(lua_State *L, Idx<Variable> variable, int index, int
variable->valueType = type;


if (queryHelper && (type == LUA_TTABLE || type == LUA_TUSERDATA || type == LUA_TFUNCTION)) {
if (queryHelper && (type == LUA_TTABLE || type == LUA_TUSERDATA || type == LUA_TFUNCTION
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

应该改为类似 && registerType[type] (registerType应该是个数组) 的方式判断类型是否需要导出判断,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你的意思是改为使用类似表驱动(或控制表, Control_table)的方式来实现?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你加的这个功能对大部分人是不需要,所以影响尽可能的要小,判断类型是否在某个数组(不是set)上性能足够的高, 另外你需要提供函数: 注册需要导入到lua中的类型. 比如对外的接口类似dbg.registerType("string")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你加的这个功能对大部分人是不需要,所以影响尽可能的要小,判断类型是否在某个数组(不是set)上性能足够的高, 另外你需要提供函数: 注册需要导入到lua中的类型. 比如对外的接口类似dbg.registerType("string")

话说,为什么使用数组而不是set?

有以下事实:

  1. std::unordered_set,基于哈希表,它的查找复杂度是O(1)
  2. std::vector,基于动态数组,它的查找复杂度是O(n)

为了高性能,是不是应该使用vector呢?
亦或,你的意思是,这里的元素量很小(n很小),哈希算法反而更耗?

Copy link
Member

@CppCXY CppCXY Feb 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::array预先申请足够的内存(大概就8)然后把type的整型值作为索引去索引目标位置是否为true, 实际上更应该使用的是std::bitset, 或者直接使用整数的位运算

@CppCXY
Copy link
Member

CppCXY commented Feb 18, 2024

另外要考虑到这个调试器是vscode和intellij公用的, 你需要同步改动到vscode, 最后我的建议是单纯修改调试器实现你需要的功能比intellij接受pr更容易, 阿唐几个月才诈尸一次,不是太容易合并pr.

@zhangjiequan
Copy link
Contributor Author

另外要考虑到这个调试器是vscode和intellij公用的, 你需要同步改动到vscode, 最后我的建议是单纯修改调试器实现你需要的功能比intellij接受pr更容易, 阿唐几个月才诈尸一次,不是太容易合并pr.

好的,我尝试提交一版单纯修改调试器的实现。

@zhangjiequan
Copy link
Contributor Author

zhangjiequan commented Feb 18, 2024

概述

已提交单纯修改调试器的实现。
(typeName记录到std::bitset中。)

说明

  1. 支持自定义Lua中所有类型数据的展示方式,可定制的类型包括:nil、boolean、lightuserdata、number、string、table、function、userdata、thread。
  2. 为实现此功能,你需要在代码中注册类型名称,使用 dbg.registerTypeName("typeName") 方法,并在自定义展示逻辑中通过 if typeName == 'typeName' 来匹配对应的类型。

测试用例

测试用例展示了如何注册并自定义Lua中“number”和“string”类型的显示方式。
通过 dbg.registerTypeName 方法注册后,我们在 emmyHelper.queryVariableCustom 函数中定义了特定类型数据的展示逻辑。
例如,对于“number”类型,我们不仅显示其数值,还以十六进制格式呈现;对于“string”类型,我们在原始字符串后追加自定义文本“(Custom!)”。
这样,开发者可以清晰地看到每种数据类型的详细信息,并根据项目需求进行个性化展示。

dbg.registerTypeName("number")
dbg.registerTypeName("string")

local emmyHelper = rawget(_G, "emmyHelper")
if emmyHelper then
    emmyHelper.queryVariableCustom = function(variable, obj, typeName, depth)
        if typeName == 'number' then
            local objStr = tostring(obj)
            local hexStr = string.format("%016x", obj)
            local finalStr = string.format("%s ([Hex]:%s)", objStr, hexStr)
            variable.value = finalStr
            return true
        elseif typeName == 'string' then
            variable.value = string.format("%s (Custom!)", obj)
            return true
        end
        return false
    end
end

local testInt = 32
local testString = "Hello World"

效果:
Snipaste_2024-02-18_21-36-21

@CppCXY CppCXY merged commit 61d8cb5 into EmmyLua:master Feb 19, 2024
1 check passed
@zhangjiequan zhangjiequan changed the title Extend queryHelper condition to include all lua types Supports customizing the display of all data types in Lua on the project side(支持在项目侧自定义Lua中所有类型数据的展示方式) Feb 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants