- 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
416 lines
15 KiB
C
416 lines
15 KiB
C
// ============================================================================
|
|
// block_serializer.c - JSON 序列化实现
|
|
// ============================================================================
|
|
|
|
#include "block_serializer.h"
|
|
#include "block_types.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
// 简单的 JSON 生成器
|
|
typedef struct {
|
|
char* buffer;
|
|
size_t size;
|
|
size_t capacity;
|
|
} JsonBuffer;
|
|
|
|
static void json_init(JsonBuffer* buf) {
|
|
buf->capacity = 4096;
|
|
buf->buffer = (char*)malloc(buf->capacity);
|
|
buf->size = 0;
|
|
buf->buffer[0] = '\0';
|
|
}
|
|
|
|
static void json_append(JsonBuffer* buf, const char* str) {
|
|
size_t len = strlen(str);
|
|
if (buf->size + len + 1 > buf->capacity) {
|
|
buf->capacity = (buf->size + len + 1) * 2;
|
|
buf->buffer = (char*)realloc(buf->buffer, buf->capacity);
|
|
}
|
|
memcpy(buf->buffer + buf->size, str, len + 1);
|
|
buf->size += len;
|
|
}
|
|
|
|
int serializer_save(BlockWorkspace* ws, const char* filepath) {
|
|
char* json = serializer_save_to_string(ws);
|
|
if (!json) return 0;
|
|
|
|
FILE* f = fopen(filepath, "w");
|
|
if (!f) {
|
|
free(json);
|
|
return 0;
|
|
}
|
|
|
|
fprintf(f, "%s", json);
|
|
fclose(f);
|
|
free(json);
|
|
return 1;
|
|
}
|
|
|
|
int serializer_load(BlockWorkspace* ws, const char* filepath) {
|
|
FILE* f = fopen(filepath, "r");
|
|
if (!f) return 0;
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
long size = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
char* json = (char*)malloc(size + 1);
|
|
fread(json, 1, size, f);
|
|
json[size] = '\0';
|
|
fclose(f);
|
|
|
|
int result = serializer_load_from_string(ws, json);
|
|
free(json);
|
|
return result;
|
|
}
|
|
|
|
char* serializer_save_to_string(BlockWorkspace* ws) {
|
|
JsonBuffer buf;
|
|
json_init(&buf);
|
|
|
|
json_append(&buf, "{\n");
|
|
json_append(&buf, " \"version\": \"1.0\",\n");
|
|
|
|
// 保存积木
|
|
json_append(&buf, " \"blocks\": [\n");
|
|
for (int i = 0; i < ws->block_count; i++) {
|
|
Block* block = ws->blocks[i];
|
|
const BlockTypeInfo* info = block_type_get_info(block->type);
|
|
|
|
char tmp[256];
|
|
snprintf(tmp, 256, " {\n");
|
|
json_append(&buf, tmp);
|
|
|
|
snprintf(tmp, 256, " \"id\": %d,\n", block->id);
|
|
json_append(&buf, tmp);
|
|
|
|
snprintf(tmp, 256, " \"type\": \"%s\",\n", info ? info->name : "unknown");
|
|
json_append(&buf, tmp);
|
|
|
|
snprintf(tmp, 256, " \"x\": %.1f,\n", block->x);
|
|
json_append(&buf, tmp);
|
|
|
|
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");
|
|
|
|
// 保存连接
|
|
json_append(&buf, " \"connections\": [\n");
|
|
for (int i = 0; i < ws->connection_count; i++) {
|
|
BlockConnection* conn = &ws->connections[i];
|
|
char tmp[128];
|
|
snprintf(tmp, 128, " {\"from\": %d, \"to\": %d}%s\n",
|
|
conn->from ? conn->from->owner->id : 0,
|
|
conn->to ? conn->to->owner->id : 0,
|
|
i < ws->connection_count - 1 ? "," : "");
|
|
json_append(&buf, tmp);
|
|
}
|
|
json_append(&buf, " ]\n");
|
|
|
|
json_append(&buf, "}\n");
|
|
|
|
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) {
|
|
workspace_clear(ws);
|
|
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;
|
|
}
|