Files
structrail-design/.trae/documents/cross-language-sdk-guide.md
skycurtain b67115b50c refactor(array-tracer): 移除 preset 指令并简化类型参数
- 将 preset 功能合并到 create 指令中,简化 API 设计
- 移除 ArrayTracerPresetCommand 类型及相关处理逻辑
- 调整 createArrayTracer 泛型参数以直接接受数组类型
- 更新相关文档以反映新的初始化模式
2026-02-04 13:33:23 +08:00

5.2 KiB
Raw Blame History

跨语言 SDK 实现指南:数据传递与序列化

背景

Structrail 平台的核心目标是提供一套语言无关的数据结构可视化协议。这意味着我们的 SDK 需要在不同特性的编程语言中(从高动态的 Python/JS 到静态底层的 C/C++)提供一致的功能体验。

注意,我们不再需要 preset 指令了,但是即便 preset 的参数被合并到 create 指令中,这篇文档对于如何在不同语言中实现 preset 功能的说明仍然是有价值的。

其中,preset 指令(一次性同步数组/容器状态)在静态类型语言(特别是 C 语言)中的实现是最大的挑战。本文档旨在论证其可行性,并为未来各语言 SDK 的实现提供参考规范。

核心挑战:数组的异构性

在动态语言TS, Python数组是自描述的对象包含长度和元素类型信息。 在静态托管语言Java, C#, Go数组/列表也是对象,具备反射或自省能力。 在 C/C++ 中,原生数组仅仅是内存地址(指针),且 C 语言完全丢失了长度和类型信息。

通用解决方案SDK 接口分层

为了抹平差异,我们建议 SDK 接口设计遵循**“渐进式暴露”**原则:

  1. Level 1: 智能推断接口(针对 TS, Python, Java 等)

    • 用户直接传数组对象。
    • SDK 内部自动获取长度、遍历序列化。
  2. Level 2: 显式元数据接口(针对 C++, Go, Rust 等)

    • 用户传递数据指针 + 长度。
    • 利用泛型/模板自动推导元素序列化逻辑。
  3. Level 3: 手动序列化接口(针对 C 语言)

    • 用户传递数据指针 + 长度 + 元素大小 + 序列化策略。

各语言实现参考

1. TypeScript / JavaScript (已实现)

利用语言的动态特性,直接接受 any[]

// SDK
preset(data: T[]) {
  // JSON.stringify 天然支持数组序列化
  this.emit('preset', { data });
}

2. Java / C# / Python

利用标准库的反射或序列化能力。

Java 示例:

// SDK
public <T> void preset(List<T> data) {
    // 使用 Jackson 或 Gson 库
    String json = gson.toJson(data);
    this.emit("preset", json);
}

3. C++ (Modern C++)

利用模板和 STL 容器特性。

C++ 示例:

// 针对 std::vector 的重载
template<typename T>
void preset(const std::vector<T>& data) {
    json j = data; // 使用 nlohmann/json 库,自动支持 STL 容器
    emit("preset", j);
}

// 针对原生数组的重载 (C++20 std::span 最佳,或传指针+长度)
template<typename T>
void preset(const T* arr, size_t size) {
    std::vector<T> vec(arr, arr + size);
    preset(vec);
}

4. C 语言 (重点攻坚)

C 语言没有泛型,没有反射,没有对象。我们需要用户手动提供元数据。

方案 A: 宏魔法 (Generic Selection) - 推荐用于基础类型

利用 C11 _Generic 关键字模拟函数重载,提升基础类型的使用体验。

// 底层 API
void _tracer_preset_int(tracer_t* t, int* arr, size_t len);
void _tracer_preset_float(tracer_t* t, float* arr, size_t len);

// 用户宏接口
#define tracer_preset(t, arr, len) _Generic((arr), \
    int*: _tracer_preset_int, \
    float*: _tracer_preset_float \
)(t, arr, len)

// 用户调用
int nums[] = {1, 2, 3};
tracer_preset(t, nums, 3); // 自动匹配 int 版本

方案 B: 格式化字符串 (Printf Style) - 推荐用于复杂类型

对于结构体或未覆盖的基础类型,采用类似 printf 的描述符。

/**
 * @param elem_fmt: 元素类型描述符
 *    "d": int
 *    "f": float
 *    "s": string
 *    "{x:d,y:d}": struct Point {int x; int y}
 */
void tracer_preset_fmt(tracer_t* t, void* data, size_t len, size_t elem_size, const char* elem_fmt);

// 用户调用
struct Point pts[] = {{1,2}, {3,4}};
tracer_preset_fmt(t, pts, 2, sizeof(struct Point), "{x:d,y:d}");

方案 C: 回调函数 (Callback) - 兜底方案

万能方案,用户自己负责把元素转成字符串。

typedef void (*serializer_func)(void* elem, char* buffer);

void tracer_preset_cb(tracer_t* t, void* data, size_t len, size_t elem_size, serializer_func cb);

// 用户调用
// 1. 用户定义序列化器
void my_int_serializer(void* elem, char* buffer) {
    sprintf(buffer, "%d", *(int*)elem);
}

// 2. 传递给 SDK
int nums[] = {1, 2, 3};
tracer_preset_cb(t, nums, 3, sizeof(int), my_int_serializer);

协议一致性保障

无论采用哪种语言实现,生成的 JSON 指令必须严格一致:

{
  "type": "ArrayTracer",
  "tracer": "uuid-...",
  "action": "preset",
  "params": {
    "data": [1, 2, 3, 4] // 或者是 [{"x":1,"y":2}, ...]
  }
}

这意味着 C 语言 SDK 内部必须手动拼接 JSON 字符串([ + elem + , + elem + ])。

结论

  1. preset 指令是可移植的:即使在最底层的 C 语言中,通过适当的 API 封装(宏或格式化串),也能实现与高级语言近似的开发体验。
  2. 不要为了 C 语言阉割协议:不需要因为 C 语言处理数组麻烦,就放弃 preset 而强迫所有语言都用 patch 循环。SDK 应该把复杂性封装在内部,留给用户简洁的接口。