docs: 添加项目设计文档与跨语言SDK实现指南
- 新增 tracer-type.md 说明 Tracer 类型及其设计理念 - 新增 project-introduction.md 介绍项目背景与目标 - 新增 cross-language-sdk-guide.md 详细说明跨语言数据传递与序列化方案 - 新增 sdk-initialization-patterns.md 提供多语言 SDK 初始化设计规范
This commit is contained in:
168
.trae/documents/cross-language-sdk-guide.md
Normal file
168
.trae/documents/cross-language-sdk-guide.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# 跨语言 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[]`。
|
||||
|
||||
```typescript
|
||||
// SDK
|
||||
preset(data: T[]) {
|
||||
// JSON.stringify 天然支持数组序列化
|
||||
this.emit('preset', { data });
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Java / C# / Python
|
||||
|
||||
利用标准库的反射或序列化能力。
|
||||
|
||||
**Java 示例**:
|
||||
|
||||
```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++ 示例**:
|
||||
|
||||
```cpp
|
||||
// 针对 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` 关键字模拟函数重载,提升基础类型的使用体验。
|
||||
|
||||
```c
|
||||
// 底层 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` 的描述符。
|
||||
|
||||
```c
|
||||
/**
|
||||
* @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) - 兜底方案
|
||||
|
||||
万能方案,用户自己负责把元素转成字符串。
|
||||
|
||||
```c
|
||||
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 指令必须严格一致:
|
||||
|
||||
```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 应该把复杂性封装在内部,留给用户简洁的接口。
|
||||
224
.trae/documents/sdk-initialization-patterns.md
Normal file
224
.trae/documents/sdk-initialization-patterns.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# 多语言 SDK 初始化设计规范
|
||||
|
||||
## 背景
|
||||
|
||||
为了简化 SDK 的使用流程并统一 API 设计体验,我们决定将 Tracer 的初始化数据(Initial Data)合并到创建(Create)阶段。这意味着用户在实例化 Tracer 时,可以直接传入初始数据,而无需单独调用 `preset` 方法。
|
||||
|
||||
这一设计模式(Construct as Initialize)在大多数现代编程语言中都有成熟的最佳实践。本文档旨在为不同语言的 SDK 实现提供具体的代码范式参考。
|
||||
|
||||
## 核心原则
|
||||
|
||||
1. **优先使用构造函数参数**:如果语言支持(如 Python, Kotlin, Swift),优先使用带默认值的命名参数。
|
||||
2. **利用语言特性**:
|
||||
- **重载 (Overloading)**:适用于 Java, C++, C#。
|
||||
- **配置对象 (Options Object)**:适用于 TS, JS, Lua。
|
||||
- **Builder / Functional Options**:适用于 Go, Rust。
|
||||
3. **保持协议底层一致**:无论上层 API 如何设计,底层生成的 `create` 指令 JSON 必须包含 `array` (或对应数据字段) 参数。
|
||||
|
||||
---
|
||||
|
||||
## 各语言实现参考
|
||||
|
||||
### 1. TypeScript / JavaScript (当前基准)
|
||||
|
||||
利用接口(Interface)定义配置对象,简洁且扩展性强。
|
||||
|
||||
```typescript
|
||||
// 定义
|
||||
interface ArrayTracerOptions<T> {
|
||||
description: string;
|
||||
array?: T[]; // 可选初始化数据
|
||||
}
|
||||
|
||||
export const createArrayTracer = <T>(options: ArrayTracerOptions<T>) => { ... }
|
||||
|
||||
// 调用
|
||||
const t1 = createArrayTracer({ description: "Empty" });
|
||||
const t2 = createArrayTracer({
|
||||
description: "My Array",
|
||||
array: [1, 2, 3]
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Python (Keyword Arguments)
|
||||
|
||||
利用 `**kwargs` 或显式关键字参数,非常符合 Pythonic 风格。
|
||||
|
||||
```python
|
||||
class ArrayTracer:
|
||||
def __init__(self, description: str, data: list = None):
|
||||
self.description = description
|
||||
if data:
|
||||
self._emit_create(data)
|
||||
|
||||
# 调用
|
||||
t1 = ArrayTracer(description="Empty")
|
||||
t2 = ArrayTracer(description="My Array", data=[1, 2, 3])
|
||||
```
|
||||
|
||||
### 3. Java (Constructor Overloading)
|
||||
|
||||
利用构造函数重载提供多种初始化路径。
|
||||
|
||||
```java
|
||||
public class ArrayTracer<T> {
|
||||
// 构造函数 1: 仅描述
|
||||
public ArrayTracer(String description) {
|
||||
this(description, null);
|
||||
}
|
||||
|
||||
// 构造函数 2: 描述 + 数据
|
||||
public ArrayTracer(String description, List<T> data) {
|
||||
// ... implementation
|
||||
}
|
||||
}
|
||||
|
||||
// 调用
|
||||
var t1 = new ArrayTracer<Integer>("Empty");
|
||||
var t2 = new ArrayTracer<Integer>("My Array", Arrays.asList(1, 2, 3));
|
||||
```
|
||||
|
||||
### 4. C++ (Overloading & Initializer List)
|
||||
|
||||
利用 `std::initializer_list` 支持花括号初始化,语法极其简洁。
|
||||
|
||||
```cpp
|
||||
template <typename T>
|
||||
class ArrayTracer {
|
||||
public:
|
||||
// 基础构造
|
||||
ArrayTracer(std::string description) { ... }
|
||||
|
||||
// 带数据构造 (支持 vector)
|
||||
ArrayTracer(std::string description, const std::vector<T>& data) { ... }
|
||||
|
||||
// 带数据构造 (支持 {1,2,3} 字面量)
|
||||
ArrayTracer(std::string description, std::initializer_list<T> data) { ... }
|
||||
};
|
||||
|
||||
// 调用
|
||||
ArrayTracer<int> t1("Empty");
|
||||
ArrayTracer<int> t2("My Array", {1, 2, 3});
|
||||
```
|
||||
|
||||
### 5. Go (Functional Options Pattern)
|
||||
|
||||
Go 社区处理复杂构造参数的标准模式。
|
||||
|
||||
```go
|
||||
type Option func(*ArrayTracer)
|
||||
|
||||
func WithData(data []interface{}) Option {
|
||||
return func(t *ArrayTracer) {
|
||||
t.initialData = data
|
||||
}
|
||||
}
|
||||
|
||||
func NewArrayTracer(desc string, opts ...Option) *ArrayTracer {
|
||||
t := &ArrayTracer{Description: desc}
|
||||
for _, opt := range opts {
|
||||
opt(t)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// 调用
|
||||
t1 := NewArrayTracer("Empty")
|
||||
t2 := NewArrayTracer("My Array", WithData([]interface{}{1, 2, 3}))
|
||||
```
|
||||
|
||||
### 6. Rust (Builder Pattern)
|
||||
|
||||
利用 Builder 模式处理构造参数,保证类型安全和可读性。
|
||||
|
||||
```rust
|
||||
struct ArrayTracerBuilder { ... }
|
||||
|
||||
impl ArrayTracer {
|
||||
pub fn builder(description: &str) -> ArrayTracerBuilder { ... }
|
||||
}
|
||||
|
||||
impl ArrayTracerBuilder {
|
||||
pub fn with_data(mut self, data: Vec<i32>) -> Self { ... }
|
||||
pub fn build(self) -> ArrayTracer { ... }
|
||||
}
|
||||
|
||||
// 调用
|
||||
let t = ArrayTracer::builder("My Array")
|
||||
.with_data(vec![1, 2, 3])
|
||||
.build();
|
||||
```
|
||||
|
||||
### 7. C# (Optional Arguments)
|
||||
|
||||
类似于 TypeScript 和 Kotlin,C# 支持命名参数和默认值。
|
||||
|
||||
```csharp
|
||||
public class ArrayTracer<T> {
|
||||
public ArrayTracer(string description, IEnumerable<T> data = null) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
// 调用
|
||||
var t1 = new ArrayTracer<int>("Empty");
|
||||
var t2 = new ArrayTracer<int>("My Array", data: new[] { 1, 2, 3 });
|
||||
```
|
||||
|
||||
### 8. C 语言 (Special Case)
|
||||
|
||||
C 语言不支持重载,且缺乏自省能力,因此建议提供两种创建模式:
|
||||
|
||||
**方案 A: 基础数据类型 (使用宏或特定后缀)**
|
||||
对于 `int`, `float` 等基础类型,提供专用函数。
|
||||
|
||||
```c
|
||||
// 基础创建
|
||||
tracer_t* tracer_create_array(const char* desc);
|
||||
|
||||
// 带数据创建 (Explicit is better than implicit)
|
||||
tracer_t* tracer_create_array_with_data(const char* desc, void* data, size_t len);
|
||||
|
||||
// 调用
|
||||
tracer_t* t1 = tracer_create_array("Empty");
|
||||
int nums[] = {1, 2, 3};
|
||||
tracer_t* t2 = tracer_create_array_with_data("My Array", nums, 3);
|
||||
```
|
||||
|
||||
**方案 B: 自定义结构体 (Callback 模式)**
|
||||
对于用户自定义的 `struct`,采用类似 `qsort` 的回调函数模式,让用户提供序列化逻辑。
|
||||
|
||||
```c
|
||||
// 定义序列化回调: 将 elem 转换为 JSON 字符串写入 buffer
|
||||
typedef void (*serializer_func)(const void* elem, char* buffer);
|
||||
|
||||
/**
|
||||
* @param serializer 用户提供的序列化函数
|
||||
*/
|
||||
tracer_t* tracer_create_array_custom(
|
||||
const char* desc,
|
||||
const void* data,
|
||||
size_t len,
|
||||
size_t elem_size,
|
||||
serializer_func serializer
|
||||
);
|
||||
|
||||
// 用户代码示例
|
||||
typedef struct { int x; int y; } Point;
|
||||
|
||||
// 用户编写序列化逻辑
|
||||
void point_serializer(const void* elem, char* buffer) {
|
||||
const Point* p = (const Point*)elem;
|
||||
sprintf(buffer, "{\"x\":%d,\"y\":%d}", p->x, p->y);
|
||||
}
|
||||
|
||||
// 调用
|
||||
Point pts[] = {{1,2}, {3,4}};
|
||||
tracer_t* t = tracer_create_array_custom("Points", pts, 2, sizeof(Point), point_serializer);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
通过统一采用**“构造即初始化”**的设计模式,我们能够在几乎所有主流编程语言中提供一致、简洁且符合语言习惯(Idiomatic)的 SDK 使用体验。这不仅降低了用户的学习成本,也使得代码更加紧凑和易读。
|
||||
Reference in New Issue
Block a user