feat: major engine overhaul - scene demo with lighting, sphere, ground
- Scene Demo: cube + sphere + ground plane with proper lighting - Audio: XAudio2 dynamic loading + WAV playback - Physics: expanded collision system - Renderer: improved pipeline with materials - UI: extended widget system - VisualScript: enhanced serializer + block interactions - ASM: optimized math/memory/profiling routines - Window: mouse input + scroll support
This commit is contained in:
1 parent
2653926d85
commit
0669d102ce
35 files changed
+1844
-314
No files matched your search
@@ -33,6 +33,30 @@ Block* block_create(BlockType type, float x, float y) {
|
||||
return block;
|
||||
}
|
||||
|
||||
// 复制积木 (深拷贝,不复制连接关系)
|
||||
Block* block_copy(const Block* src) {
|
||||
if (!src) return NULL;
|
||||
Block* block = (Block*)malloc(sizeof(Block));
|
||||
if (!block) return NULL;
|
||||
memcpy(block, src, sizeof(Block));
|
||||
|
||||
// 重新设置拥有者,断开所有连接
|
||||
for (int i = 0; i < MAX_BLOCK_INPUTS; i++) {
|
||||
block->inputs[i].owner = block;
|
||||
block->inputs[i].connected = NULL;
|
||||
}
|
||||
for (int i = 0; i < MAX_BLOCK_OUTPUTS; i++) {
|
||||
block->outputs[i].owner = block;
|
||||
block->outputs[i].connected = NULL;
|
||||
}
|
||||
block->next = NULL;
|
||||
block->parent_if = NULL;
|
||||
block->workspace = NULL;
|
||||
block->user_data = NULL;
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
// 销毁积木
|
||||
void block_destroy(Block* block) {
|
||||
if (!block) return;
|
||||
|
||||
@@ -171,6 +171,7 @@ typedef struct BlockConnection {
|
||||
|
||||
// 函数声明
|
||||
Block* block_create(BlockType type, float x, float y);
|
||||
Block* block_copy(const Block* src);
|
||||
void block_destroy(Block* block);
|
||||
BlockSlot* block_add_input(Block* block, SlotKind kind, SlotValueType value_type, const char* label);
|
||||
BlockSlot* block_add_output(Block* block, SlotKind kind, SlotValueType value_type, const char* label);
|
||||
|
||||
@@ -51,6 +51,7 @@ void executor_destroy(BlockExecutor* exec) { free(exec); }
|
||||
void executor_start(BlockExecutor* exec) {
|
||||
exec->state = EXEC_RUNNING;
|
||||
exec->call_stack_top = 0;
|
||||
exec->loop_stack_top = 0;
|
||||
exec->current_block = NULL;
|
||||
for (int i = 0; i < exec->workspace->block_count; i++) {
|
||||
Block* block = exec->workspace->blocks[i];
|
||||
@@ -76,6 +77,26 @@ static Block* next_from_output(Block* block, int output_index) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 查找循环体链的最后一个积木(用于确定循环结束后应跳转到的积木)
|
||||
static Block* find_loop_body_end(Block* loop_block) {
|
||||
if (!loop_block || loop_block->output_count == 0 || !loop_block->outputs[0].connected) return NULL;
|
||||
Block* cur = loop_block->outputs[0].connected->owner;
|
||||
Block* prev = loop_block;
|
||||
int guard = 0;
|
||||
while (cur && guard < 1000) {
|
||||
guard++;
|
||||
if (cur == loop_block) return prev; // 回到循环头,则前一个为体尾
|
||||
Block* next = NULL;
|
||||
if (cur->output_count > 0 && cur->outputs[0].connected) {
|
||||
next = cur->outputs[0].connected->owner;
|
||||
}
|
||||
if (!next) return cur;
|
||||
prev = cur;
|
||||
cur = next;
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
// 获取值输入
|
||||
static SlotValue get_input(Block* block, int idx) {
|
||||
if (idx >= block->input_count) return make_number(0);
|
||||
@@ -174,22 +195,54 @@ void executor_step(BlockExecutor* exec) {
|
||||
next = next_from_output(cur, cond ? 0 : 1);
|
||||
break;
|
||||
}
|
||||
case BLOCK_CONTROL_REPEAT: {
|
||||
case BLOCK_CONTROL_REPEAT:
|
||||
case BLOCK_CONTROL_WHILE: {
|
||||
int cond = get_input(cur, 1).as.boolean;
|
||||
if (cond) next = next_from_output(cur, 0);
|
||||
else next = next_from_output(cur, 0); // 退出循环: 走输出
|
||||
LoopFrame* frame = NULL;
|
||||
if (exec->loop_stack_top > 0 && exec->loop_stack[exec->loop_stack_top - 1].loop_block == cur) {
|
||||
frame = &exec->loop_stack[exec->loop_stack_top - 1];
|
||||
} else {
|
||||
if (exec->loop_stack_top < MAX_LOOP_STACK) {
|
||||
frame = &exec->loop_stack[exec->loop_stack_top++];
|
||||
frame->loop_block = cur;
|
||||
frame->counter = 0;
|
||||
frame->target = 0;
|
||||
Block* body_end = find_loop_body_end(cur);
|
||||
frame->after_block = body_end ? next_from_output(body_end, 0) : NULL;
|
||||
}
|
||||
}
|
||||
if (frame && cond) {
|
||||
frame->counter++;
|
||||
next = next_from_output(cur, 0);
|
||||
} else {
|
||||
if (frame) exec->loop_stack_top--;
|
||||
next = frame ? frame->after_block : next_from_output(cur, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BLOCK_CONTROL_REPEAT_TIMES: {
|
||||
double times = get_input(cur, 1).as.number;
|
||||
// 简化: 只执行一次,实际需要计数器
|
||||
log_info("循环 %.0f 次 (简化: 执行一次)", times);
|
||||
next = next_from_output(cur, 0);
|
||||
break;
|
||||
}
|
||||
case BLOCK_CONTROL_WHILE: {
|
||||
int cond = get_input(cur, 1).as.boolean;
|
||||
if (cond) next = next_from_output(cur, 0);
|
||||
LoopFrame* frame = NULL;
|
||||
if (exec->loop_stack_top > 0 && exec->loop_stack[exec->loop_stack_top - 1].loop_block == cur) {
|
||||
frame = &exec->loop_stack[exec->loop_stack_top - 1];
|
||||
frame->counter++;
|
||||
} else {
|
||||
if (exec->loop_stack_top < MAX_LOOP_STACK) {
|
||||
frame = &exec->loop_stack[exec->loop_stack_top++];
|
||||
frame->loop_block = cur;
|
||||
frame->counter = 0;
|
||||
frame->target = (int)times;
|
||||
Block* body_end = find_loop_body_end(cur);
|
||||
frame->after_block = body_end ? next_from_output(body_end, 0) : NULL;
|
||||
}
|
||||
}
|
||||
if (frame && frame->counter < frame->target) {
|
||||
log_info("循环 %d / %d", frame->counter + 1, frame->target);
|
||||
next = next_from_output(cur, 0);
|
||||
} else {
|
||||
if (frame) exec->loop_stack_top--;
|
||||
next = frame ? frame->after_block : next_from_output(cur, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BLOCK_CONTROL_WAIT: {
|
||||
@@ -320,19 +373,32 @@ void executor_step(BlockExecutor* exec) {
|
||||
case BLOCK_FUNC_CALL: {
|
||||
const char* fname = get_input(cur, 1).as.string;
|
||||
log_info("调用函数 '%s'", fname);
|
||||
if (exec->call_stack_top < MAX_CALL_STACK) {
|
||||
|
||||
// 在工作区中查找同名函数定义积木
|
||||
Block* def = NULL;
|
||||
for (int i = 0; i < exec->workspace->block_count; i++) {
|
||||
Block* b = exec->workspace->blocks[i];
|
||||
if (b->type == BLOCK_CONTROL_FUNCTION_DEF && strcmp(b->label, fname) == 0) {
|
||||
def = b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (def && exec->call_stack_top < MAX_CALL_STACK) {
|
||||
exec->call_stack[exec->call_stack_top].block = cur;
|
||||
exec->call_stack[exec->call_stack_top].return_block = next_from_output(cur, 0);
|
||||
exec->call_stack_top++;
|
||||
next = next_from_output(def, 0);
|
||||
} else {
|
||||
if (!def) log_error("未找到函数定义: '%s'", fname);
|
||||
next = next_from_output(cur, 0);
|
||||
}
|
||||
// TODO: 查找函数定义积木并跳转
|
||||
next = next_from_output(cur, 0);
|
||||
break;
|
||||
}
|
||||
case BLOCK_FUNC_RETURN: {
|
||||
if (exec->call_stack_top > 0) {
|
||||
exec->call_stack_top--;
|
||||
Block* caller = exec->call_stack[exec->call_stack_top].block;
|
||||
next = next_from_output(caller, 0);
|
||||
next = exec->call_stack[exec->call_stack_top].return_block;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -343,6 +409,14 @@ void executor_step(BlockExecutor* exec) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 若当前 next 是某个活跃循环的 after_block,则跳转回循环头继续判断
|
||||
if (next && exec->loop_stack_top > 0) {
|
||||
LoopFrame* frame = &exec->loop_stack[exec->loop_stack_top - 1];
|
||||
if (next == frame->after_block) {
|
||||
next = frame->loop_block;
|
||||
}
|
||||
}
|
||||
|
||||
exec->current_block = next;
|
||||
if (!exec->current_block) {
|
||||
log_info("执行完成");
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#define MAX_VARIABLES 256
|
||||
#define MAX_CALL_STACK 32
|
||||
#define MAX_EXEC_STACK 1024
|
||||
#define MAX_LOOP_STACK 32
|
||||
|
||||
// 变量
|
||||
typedef struct {
|
||||
@@ -23,6 +24,14 @@ typedef struct {
|
||||
Block* return_block;
|
||||
} CallFrame;
|
||||
|
||||
// 循环帧
|
||||
typedef struct {
|
||||
Block* loop_block;
|
||||
int counter;
|
||||
int target;
|
||||
Block* after_block;
|
||||
} LoopFrame;
|
||||
|
||||
// 执行状态
|
||||
typedef enum {
|
||||
EXEC_IDLE = 0,
|
||||
@@ -43,6 +52,9 @@ typedef struct BlockExecutor {
|
||||
CallFrame call_stack[MAX_CALL_STACK];
|
||||
int call_stack_top;
|
||||
|
||||
LoopFrame loop_stack[MAX_LOOP_STACK];
|
||||
int loop_stack_top;
|
||||
|
||||
Block* current_block;
|
||||
double wait_timer;
|
||||
|
||||
|
||||
@@ -116,11 +116,25 @@ void interaction_key_down(BlockInteraction* inter, BlockWorkspace* ws, int key)
|
||||
}
|
||||
|
||||
// 复制/粘贴 (Ctrl+C / Ctrl+V)
|
||||
static Block* clipboard = NULL;
|
||||
if (key == 'C' && (GetAsyncKeyState(VK_CONTROL) & 0x8000)) {
|
||||
// TODO: 复制到剪贴板
|
||||
if (inter->selected_block) {
|
||||
if (clipboard) block_destroy(clipboard);
|
||||
clipboard = block_copy(inter->selected_block);
|
||||
}
|
||||
}
|
||||
if (key == 'V' && (GetAsyncKeyState(VK_CONTROL) & 0x8000)) {
|
||||
// TODO: 从剪贴板粘贴
|
||||
if (clipboard) {
|
||||
Block* copy = block_copy(clipboard);
|
||||
if (copy) {
|
||||
copy->x += 20.0f;
|
||||
copy->y += 20.0f;
|
||||
copy->id = ws->next_block_id++;
|
||||
workspace_add_block(ws, copy);
|
||||
inter->selected_block = copy;
|
||||
ws->selected_block = copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
#include "block_core.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
// 5x7 位图字体 (列优先, LSB 在上)
|
||||
#include "block_font.inc"
|
||||
|
||||
int block_renderer_init(BlockRenderer* renderer, void* device, void* context) {
|
||||
memset(renderer, 0, sizeof(BlockRenderer));
|
||||
@@ -94,10 +98,30 @@ void block_renderer_add_line(BlockRenderer* renderer, float x1, float y1, float
|
||||
float dy = y2 - y1;
|
||||
float len = sqrtf(dx * dx + dy * dy);
|
||||
if (len < 0.001f) return;
|
||||
float nx = -dy / len;
|
||||
float ny = dx / len;
|
||||
|
||||
block_renderer_add_quad(renderer, x1, y1, x2 - x1, y2 - y1, r, g, b, a);
|
||||
const float thickness = 1.0f;
|
||||
float nx = -dy / len * thickness * 0.5f;
|
||||
float ny = dx / len * thickness * 0.5f;
|
||||
|
||||
if (renderer->vertex_count + 4 > renderer->vertex_capacity) return;
|
||||
if (renderer->index_count + 6 > renderer->index_capacity) return;
|
||||
|
||||
uint32_t base = renderer->vertex_count;
|
||||
float* v = renderer->vertex_data + base * 7;
|
||||
|
||||
v[0*7+0] = x1 + nx; v[0*7+1] = y1 + ny; v[0*7+2] = 0; v[0*7+3] = r; v[0*7+4] = g; v[0*7+5] = b; v[0*7+6] = a;
|
||||
v[1*7+0] = x2 + nx; v[1*7+1] = y2 + ny; v[1*7+2] = 0; v[1*7+3] = r; v[1*7+4] = g; v[1*7+5] = b; v[1*7+6] = a;
|
||||
v[2*7+0] = x2 - nx; v[2*7+1] = y2 - ny; v[2*7+2] = 0; v[2*7+3] = r; v[2*7+4] = g; v[2*7+5] = b; v[2*7+6] = a;
|
||||
v[3*7+0] = x1 - nx; v[3*7+1] = y1 - ny; v[3*7+2] = 0; v[3*7+3] = r; v[3*7+4] = g; v[3*7+5] = b; v[3*7+6] = a;
|
||||
|
||||
renderer->vertex_count += 4;
|
||||
|
||||
renderer->index_data[renderer->index_count++] = base + 0;
|
||||
renderer->index_data[renderer->index_count++] = base + 1;
|
||||
renderer->index_data[renderer->index_count++] = base + 2;
|
||||
renderer->index_data[renderer->index_count++] = base + 0;
|
||||
renderer->index_data[renderer->index_count++] = base + 2;
|
||||
renderer->index_data[renderer->index_count++] = base + 3;
|
||||
}
|
||||
|
||||
static void block_renderer_add_bezier(BlockRenderer* renderer, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, float r, float g, float b, float a) {
|
||||
@@ -122,6 +146,10 @@ void block_renderer_draw_block(BlockRenderer* renderer, Block* block, float view
|
||||
|
||||
block_renderer_add_quad(renderer, x, y, w, h, 0.3f, 0.3f, 0.35f, 1.0f);
|
||||
block_renderer_add_quad(renderer, x, y, w, 20 * zoom, 0.2f, 0.2f, 0.25f, 1.0f);
|
||||
|
||||
if (block->label[0]) {
|
||||
block_renderer_draw_text(renderer, block->label, x + 4.0f * zoom, y + 4.0f * zoom, 0.9f, 0.9f, 0.9f);
|
||||
}
|
||||
}
|
||||
|
||||
void block_renderer_draw_connection(BlockRenderer* renderer, BlockSlot* from, BlockSlot* to, float view_x, float view_y, float zoom) {
|
||||
@@ -138,5 +166,25 @@ void block_renderer_draw_connection(BlockRenderer* renderer, BlockSlot* from, Bl
|
||||
|
||||
void block_renderer_draw_text(BlockRenderer* renderer, const char* text, float x, float y, float r, float g, float b) {
|
||||
if (!text) return;
|
||||
(void)renderer; (void)x; (void)y; (void)r; (void)g; (void)b;
|
||||
const float scale = 2.0f;
|
||||
const float advance = 5.0f * scale + 1.0f;
|
||||
float cx = x;
|
||||
for (const char* p = text; *p; ++p) {
|
||||
unsigned char ch = (unsigned char)*p;
|
||||
if (ch < 32 || ch > 127) ch = '?';
|
||||
int idx = ch - 32;
|
||||
for (int col = 0; col < 5; ++col) {
|
||||
uint8_t column = BLOCK_FONT[idx][col];
|
||||
for (int row = 0; row < 7; ++row) {
|
||||
if (column & (1u << row)) {
|
||||
block_renderer_add_quad(renderer,
|
||||
cx + col * scale,
|
||||
y + row * scale,
|
||||
scale, scale,
|
||||
r, g, b, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
cx += advance;
|
||||
}
|
||||
}
|
||||
@@ -32,12 +32,6 @@ static void json_append(JsonBuffer* buf, const char* str) {
|
||||
buf->size += len;
|
||||
}
|
||||
|
||||
static void json_free(JsonBuffer* buf) {
|
||||
free(buf->buffer);
|
||||
buf->buffer = NULL;
|
||||
buf->size = 0;
|
||||
}
|
||||
|
||||
int serializer_save(BlockWorkspace* ws, const char* filepath) {
|
||||
char* json = serializer_save_to_string(ws);
|
||||
if (!json) return 0;
|
||||
@@ -98,9 +92,38 @@ char* serializer_save_to_string(BlockWorkspace* ws) {
|
||||
snprintf(tmp, 256, " \"x\": %.1f,\n", block->x);
|
||||
json_append(&buf, tmp);
|
||||
|
||||
snprintf(tmp, 256, " \"y\": %.1f\n", block->y);
|
||||
snprintf(tmp, 256, " \"y\": %.1f,\n", block->y);
|
||||
json_append(&buf, tmp);
|
||||
|
||||
|
||||
snprintf(tmp, 256, " \"label\": \"%s\"", block->label);
|
||||
json_append(&buf, tmp);
|
||||
|
||||
// 保存输入槽默认值
|
||||
if (block->input_count > 0) {
|
||||
json_append(&buf, ",\n \"inputs\": [\n");
|
||||
for (int j = 0; j < block->input_count; j++) {
|
||||
BlockSlot* slot = &block->inputs[j];
|
||||
SlotValue* val = &slot->value;
|
||||
switch (val->type) {
|
||||
case SLOT_VALUE_NUMBER:
|
||||
snprintf(tmp, 256, " {\"type\": \"number\", \"value\": %.6g}%s\n", val->as.number, j < block->input_count - 1 ? "," : "");
|
||||
break;
|
||||
case SLOT_VALUE_STRING:
|
||||
snprintf(tmp, 256, " {\"type\": \"string\", \"value\": \"%s\"}%s\n", val->as.string, j < block->input_count - 1 ? "," : "");
|
||||
break;
|
||||
case SLOT_VALUE_BOOLEAN:
|
||||
snprintf(tmp, 256, " {\"type\": \"boolean\", \"value\": %s}%s\n", val->as.boolean ? "true" : "false", j < block->input_count - 1 ? "," : "");
|
||||
break;
|
||||
default:
|
||||
snprintf(tmp, 256, " {\"type\": \"number\", \"value\": 0}%s\n", j < block->input_count - 1 ? "," : "");
|
||||
break;
|
||||
}
|
||||
json_append(&buf, tmp);
|
||||
}
|
||||
json_append(&buf, " ]");
|
||||
}
|
||||
|
||||
json_append(&buf, "\n");
|
||||
json_append(&buf, i < ws->block_count - 1 ? " },\n" : " }\n");
|
||||
}
|
||||
json_append(&buf, " ],\n");
|
||||
@@ -123,20 +146,270 @@ char* serializer_save_to_string(BlockWorkspace* ws) {
|
||||
return buf.buffer;
|
||||
}
|
||||
|
||||
// 跳过空白字符
|
||||
static const char* skip_ws(const char* p) {
|
||||
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++;
|
||||
return p;
|
||||
}
|
||||
|
||||
// 查找字符串值,返回指向引号内内容的指针,并在 *len 中设置长度
|
||||
static const char* parse_json_string(const char* p, int* len) {
|
||||
p = skip_ws(p);
|
||||
if (*p != '"') return NULL;
|
||||
p++;
|
||||
const char* start = p;
|
||||
while (*p && *p != '"') p++;
|
||||
if (*p != '"') return NULL;
|
||||
*len = (int)(p - start);
|
||||
return start;
|
||||
}
|
||||
|
||||
// 查找数字值,返回指向数字开头的指针
|
||||
static const char* parse_json_number(const char* p, float* out) {
|
||||
p = skip_ws(p);
|
||||
const char* start = p;
|
||||
if (*p == '-') p++;
|
||||
while (*p >= '0' && *p <= '9') p++;
|
||||
if (*p == '.') {
|
||||
p++;
|
||||
while (*p >= '0' && *p <= '9') p++;
|
||||
}
|
||||
if (p == start) return NULL;
|
||||
*out = (float)strtod(start, NULL);
|
||||
return p;
|
||||
}
|
||||
|
||||
int serializer_load_from_string(BlockWorkspace* ws, const char* json_str) {
|
||||
// 简化的 JSON 解析
|
||||
// 完整实现需要完整的 JSON 解析器
|
||||
|
||||
workspace_clear(ws);
|
||||
|
||||
// 查找 "blocks" 数组
|
||||
const char* blocks_start = strstr(json_str, "\"blocks\"");
|
||||
if (!blocks_start) return 0;
|
||||
|
||||
// 简化解析 - 只提取基本信息
|
||||
// 实际项目中应使用 cJSON 或类似库
|
||||
|
||||
// TODO: 实现完整的 JSON 解析
|
||||
|
||||
block_types_init();
|
||||
|
||||
// 解析 blocks 数组
|
||||
const char* blocks_key = strstr(json_str, "\"blocks\"");
|
||||
if (!blocks_key) return 0;
|
||||
const char* p = blocks_key + 8;
|
||||
p = skip_ws(p);
|
||||
if (*p != ':') return 0;
|
||||
p++;
|
||||
p = skip_ws(p);
|
||||
if (*p != '[') return 0;
|
||||
p++;
|
||||
|
||||
while (1) {
|
||||
p = skip_ws(p);
|
||||
if (*p == ']') { p++; break; }
|
||||
if (*p != '{') return 0;
|
||||
p++;
|
||||
|
||||
int id = 0;
|
||||
char type_name[64] = {0};
|
||||
char label[64] = {0};
|
||||
float x = 0, y = 0;
|
||||
SlotValue input_values[MAX_BLOCK_INPUTS];
|
||||
int input_count = 0;
|
||||
|
||||
while (1) {
|
||||
p = skip_ws(p);
|
||||
if (*p == '}') { p++; break; }
|
||||
|
||||
int key_len = 0;
|
||||
const char* key = parse_json_string(p, &key_len);
|
||||
if (!key) return 0;
|
||||
p = key + key_len + 1;
|
||||
p = skip_ws(p);
|
||||
if (*p != ':') return 0;
|
||||
p++;
|
||||
|
||||
if (key_len == 2 && strncmp(key, "id", 2) == 0) {
|
||||
float fv; p = parse_json_number(p, &fv); id = (int)fv;
|
||||
} else if (key_len == 4 && strncmp(key, "type", 4) == 0) {
|
||||
int val_len = 0;
|
||||
const char* val = parse_json_string(p, &val_len);
|
||||
if (val) {
|
||||
if (val_len >= (int)sizeof(type_name)) val_len = (int)sizeof(type_name) - 1;
|
||||
strncpy(type_name, val, val_len);
|
||||
type_name[val_len] = '\0';
|
||||
p = val + val_len + 1;
|
||||
}
|
||||
} else if (key_len == 1 && *key == 'x') {
|
||||
p = parse_json_number(p, &x);
|
||||
} else if (key_len == 1 && *key == 'y') {
|
||||
p = parse_json_number(p, &y);
|
||||
} else if (key_len == 5 && strncmp(key, "label", 5) == 0) {
|
||||
int val_len = 0;
|
||||
const char* val = parse_json_string(p, &val_len);
|
||||
if (val) {
|
||||
if (val_len >= (int)sizeof(label)) val_len = (int)sizeof(label) - 1;
|
||||
strncpy(label, val, val_len);
|
||||
label[val_len] = '\0';
|
||||
p = val + val_len + 1;
|
||||
}
|
||||
} else if (key_len == 6 && strncmp(key, "inputs", 6) == 0) {
|
||||
p = skip_ws(p);
|
||||
if (*p == '[') {
|
||||
p++;
|
||||
input_count = 0;
|
||||
while (1) {
|
||||
p = skip_ws(p);
|
||||
if (*p == ']') { p++; break; }
|
||||
if (*p != '{') break;
|
||||
p++;
|
||||
|
||||
char iv_type[16] = {0};
|
||||
float fv = 0;
|
||||
int bv = 0;
|
||||
char sval_str[256] = {0};
|
||||
while (1) {
|
||||
p = skip_ws(p);
|
||||
if (*p == '}') { p++; break; }
|
||||
int ik_len = 0;
|
||||
const char* ik = parse_json_string(p, &ik_len);
|
||||
if (!ik) break;
|
||||
p = ik + ik_len + 1;
|
||||
p = skip_ws(p);
|
||||
if (*p != ':') break;
|
||||
p++;
|
||||
|
||||
if (ik_len == 4 && strncmp(ik, "type", 4) == 0) {
|
||||
int iv_len = 0;
|
||||
const char* iv = parse_json_string(p, &iv_len);
|
||||
if (iv) {
|
||||
if (iv_len >= (int)sizeof(iv_type)) iv_len = (int)sizeof(iv_type) - 1;
|
||||
strncpy(iv_type, iv, iv_len);
|
||||
iv_type[iv_len] = '\0';
|
||||
p = iv + iv_len + 1;
|
||||
}
|
||||
} else if (ik_len == 5 && strncmp(ik, "value", 5) == 0) {
|
||||
p = skip_ws(p);
|
||||
if (*p == '"') {
|
||||
int sv_len = 0;
|
||||
const char* sval = parse_json_string(p, &sv_len);
|
||||
if (sval) {
|
||||
if (sv_len >= (int)sizeof(sval_str)) sv_len = (int)sizeof(sval_str) - 1;
|
||||
strncpy(sval_str, sval, sv_len);
|
||||
sval_str[sv_len] = '\0';
|
||||
p = sval + sv_len + 1;
|
||||
}
|
||||
} else if (strncmp(p, "true", 4) == 0) {
|
||||
bv = 1; p += 4;
|
||||
} else if (strncmp(p, "false", 5) == 0) {
|
||||
bv = 0; p += 5;
|
||||
} else {
|
||||
p = parse_json_number(p, &fv);
|
||||
}
|
||||
} else {
|
||||
while (*p && *p != ',' && *p != '}') p++;
|
||||
}
|
||||
p = skip_ws(p);
|
||||
if (*p == ',') p++;
|
||||
}
|
||||
// 暂存输入值
|
||||
if (input_count < MAX_BLOCK_INPUTS) {
|
||||
SlotValue* sv = &input_values[input_count++];
|
||||
memset(sv, 0, sizeof(SlotValue));
|
||||
if (strcmp(iv_type, "number") == 0) {
|
||||
sv->type = SLOT_VALUE_NUMBER;
|
||||
sv->as.number = (double)fv;
|
||||
} else if (strcmp(iv_type, "string") == 0) {
|
||||
sv->type = SLOT_VALUE_STRING;
|
||||
strncpy(sv->as.string, sval_str, sizeof(sv->as.string) - 1);
|
||||
} else if (strcmp(iv_type, "boolean") == 0) {
|
||||
sv->type = SLOT_VALUE_BOOLEAN;
|
||||
sv->as.boolean = bv;
|
||||
} else {
|
||||
sv->type = SLOT_VALUE_NUMBER;
|
||||
sv->as.number = 0;
|
||||
}
|
||||
}
|
||||
p = skip_ws(p);
|
||||
if (*p == ',') p++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 跳过未知值 (简单跳过直到逗号或右花括号)
|
||||
while (*p && *p != ',' && *p != '}') p++;
|
||||
}
|
||||
|
||||
p = skip_ws(p);
|
||||
if (*p == ',') p++;
|
||||
}
|
||||
|
||||
BlockType type = block_type_find_by_name(type_name);
|
||||
if (type == BLOCK_TYPE_COUNT || type_name[0] == '\0') {
|
||||
type = BLOCK_EVENT_START; // fallback
|
||||
}
|
||||
|
||||
Block* block = block_create(type, x, y);
|
||||
if (block) {
|
||||
block->id = id;
|
||||
strncpy(block->label, label, sizeof(block->label) - 1);
|
||||
block->label[sizeof(block->label) - 1] = '\0';
|
||||
for (int j = 0; j < input_count && j < block->input_count; j++) {
|
||||
block->inputs[j].value = input_values[j];
|
||||
}
|
||||
workspace_add_block(ws, block);
|
||||
if (id >= ws->next_block_id) ws->next_block_id = id + 1;
|
||||
}
|
||||
|
||||
p = skip_ws(p);
|
||||
if (*p == ',') p++;
|
||||
}
|
||||
|
||||
// 解析 connections 数组
|
||||
const char* conn_key = strstr(json_str, "\"connections\"");
|
||||
if (conn_key) {
|
||||
p = conn_key + 13;
|
||||
p = skip_ws(p);
|
||||
if (*p == ':') {
|
||||
p++;
|
||||
p = skip_ws(p);
|
||||
if (*p == '[') {
|
||||
p++;
|
||||
while (1) {
|
||||
p = skip_ws(p);
|
||||
if (*p == ']') { p++; break; }
|
||||
if (*p != '{') break;
|
||||
p++;
|
||||
|
||||
int from_id = -1, to_id = -1;
|
||||
while (1) {
|
||||
p = skip_ws(p);
|
||||
if (*p == '}') { p++; break; }
|
||||
|
||||
int key_len = 0;
|
||||
const char* key = parse_json_string(p, &key_len);
|
||||
if (!key) break;
|
||||
p = key + key_len + 1;
|
||||
p = skip_ws(p);
|
||||
if (*p != ':') break;
|
||||
p++;
|
||||
|
||||
float fv = 0;
|
||||
if (key_len == 4 && strncmp(key, "from", 4) == 0) {
|
||||
p = parse_json_number(p, &fv); from_id = (int)fv;
|
||||
} else if (key_len == 2 && strncmp(key, "to", 2) == 0) {
|
||||
p = parse_json_number(p, &fv); to_id = (int)fv;
|
||||
} else {
|
||||
while (*p && *p != ',' && *p != '}') p++;
|
||||
}
|
||||
|
||||
p = skip_ws(p);
|
||||
if (*p == ',') p++;
|
||||
}
|
||||
|
||||
if (from_id >= 0 && to_id >= 0) {
|
||||
Block* from_block = workspace_find_block(ws, from_id);
|
||||
Block* to_block = workspace_find_block(ws, to_id);
|
||||
if (from_block && to_block && from_block->output_count > 0 && to_block->input_count > 0) {
|
||||
workspace_add_connection(ws, &from_block->outputs[0], &to_block->inputs[0]);
|
||||
}
|
||||
}
|
||||
|
||||
p = skip_ws(p);
|
||||
if (*p == ',') p++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
Reference in new issue
Block a user