From b8817fd2c9fe504b52c180ae4bb1f2c0b0aa8cd6 Mon Sep 17 00:00:00 2001 From: codetypes Date: Wed, 19 Mar 2025 09:38:03 +0800 Subject: [PATCH] check group and var on build --- electron/main/index.ts | 14 +++++- public/locales/en.json | 1 + public/locales/zh.json | 1 + sample/vars/test-vars.json | 27 +++--------- src/components/editor.tsx | 48 +++++++++++++++------ src/components/register-node.ts | 8 ++-- src/contexts/workspace-context.ts | 43 ++++++++++--------- src/misc/b3type.ts | 9 ++++ src/misc/b3util.ts | 71 ++++++++++++++++++++++++++++++- src/misc/util.ts | 3 +- 10 files changed, 164 insertions(+), 61 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index da6e2f4..316baf2 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -5,6 +5,7 @@ import os from "node:os"; import path from "node:path"; import { argv } from "node:process"; import { fileURLToPath } from "node:url"; +import { VERSION, type FileVarDecl } from "../../src/misc/b3type"; import * as b3util from "../../src/misc/b3util"; import Path from "../../src/misc/path"; @@ -30,7 +31,7 @@ for (let i = 0; i < argv.length; i++) { } const printHelp = () => { - console.log("Usage: Behavior3 Editor [options]"); + console.log(`Usage: Behavior3 Editor ${VERSION} [options]`); console.log("Options:"); console.log(" --project Set the project path"); console.log(" --build Set the build output path"); @@ -64,11 +65,20 @@ if (buildOutput || buildProject || buildHelp) { if (path.endsWith(".json")) { const buildpath = buildDir + "/" + path.substring(workdir.length + 1); const treeModel = b3util.createBuildData(path); - if (treeModel && treeModel.export === false) { + if (!treeModel) { + continue; + } + if (treeModel.export === false) { console.log("skip:", buildpath); continue; } console.log("build:", buildpath); + const declare: FileVarDecl = { + import: treeModel.import.map((v) => ({ path: v, vars: [], depends: [] })), + declvar: treeModel.declvar.map((v) => ({ name: v.name, desc: v.desc })), + subtree: [], + }; + b3util.refreshDeclare(treeModel, declare); if (!b3util.checkNodeData(treeModel?.root)) { hasError = true; } diff --git a/public/locales/en.json b/public/locales/en.json index d17263d..58766b3 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -2,6 +2,7 @@ "about": "About Behavior3", "add": "Add", "actuaSize": "Actual Size", + "alertNewVersion": "This file is created by a newer version of Behavior3({{version}}), please upgrade to the latest version.", "build": "Build", "batch": "Batch Script", "batchCompleted": "Batch completed", diff --git a/public/locales/zh.json b/public/locales/zh.json index c23915c..f067f7b 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -2,6 +2,7 @@ "about": "关于 Behavior3", "add": "添加", "actualSize": "实际大小", + "alertNewVersion": "此文件由新版本 Behavior3({{version}}) 创建,请升级到最新版本。", "build": "构建", "batch": "批处理脚本", "batchCompleted": "批处理完成", diff --git a/sample/vars/test-vars.json b/sample/vars/test-vars.json index d71bf8a..44648f7 100644 --- a/sample/vars/test-vars.json +++ b/sample/vars/test-vars.json @@ -3,13 +3,8 @@ "desc": "", "firstid": 1, "export": true, - "group": [ - "Client" - ], - "import": [ - "vars/declare-core.json", - "vars/declare-vars.json" - ], + "group": ["Client"], + "import": ["vars/declare-core.json", "vars/declare-vars.json"], "declvar": [ { "name": "target", @@ -33,16 +28,12 @@ "id": 4, "name": "FindEnemy", "args": {}, - "output": [ - "target1" - ] + "output": ["target1"] }, { "id": 5, "name": "Attack", - "input": [ - "target1" - ] + "input": ["target1"] }, { "id": 6, @@ -54,16 +45,12 @@ { "id": 7, "name": "MoveToTarget", - "input": [ - "target" - ] + "input": ["target"] }, { "id": 8, "name": "Clear", - "output": [ - "test" - ], + "output": ["test"], "children": [ { "id": 9, @@ -73,4 +60,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/components/editor.tsx b/src/components/editor.tsx index 76ba806..f95da72 100644 --- a/src/components/editor.tsx +++ b/src/components/editor.tsx @@ -21,7 +21,15 @@ import { EditTree, useWorkspace, } from "../contexts/workspace-context"; -import { ImportDef, isExprType, NodeModel, TreeGraphData, TreeModel, VarDef } from "../misc/b3type"; +import { + ImportDef, + isExprType, + NodeModel, + TreeGraphData, + TreeModel, + VarDef, + VERSION, +} from "../misc/b3type"; import * as b3util from "../misc/b3util"; import { message } from "../misc/hooks"; import i18n from "../misc/i18n"; @@ -777,20 +785,32 @@ export const Editor: FC = ({ onUpdate: updateState, data: editor, . }; const save = () => { - if (editor.unsave || !fs.existsSync(editor.path)) { - const path = editor.path; - editor.autoId = b3util.refreshTreeDataId(editor.root, editor.data.firstid); - editor.data.root = b3util.createFileData(editor.root); - editor.modifiedTime = Date.now(); - fs.writeFileSync(path, JSON.stringify(editor.data, null, 2)); - workspace.updateFileMeta(editor); - editor.unsave = false; - editor.graph.changeData(editor.root); - editor.graph.layout(); - restoreViewport(); - updateSearchState(); - updateState(); + if (b3util.isNewVersion(editor.data.version)) { + message.error(t("alertNewVersion", { version: editor.data.version }), 6); + return; } + const path = editor.path; + editor.autoId = b3util.refreshTreeDataId(editor.root, editor.data.firstid); + editor.data.root = b3util.createFileData(editor.root); + editor.modifiedTime = Date.now(); + fs.writeFileSync( + path, + JSON.stringify( + { + ...editor.data, + version: VERSION, + }, + null, + 2 + ) + ); + workspace.updateFileMeta(editor); + editor.unsave = false; + editor.graph.changeData(editor.root); + editor.graph.layout(); + restoreViewport(); + updateSearchState(); + updateState(); }; const editSubtree = () => { diff --git a/src/components/register-node.ts b/src/components/register-node.ts index ef1dc53..13c62e2 100644 --- a/src/components/register-node.ts +++ b/src/components/register-node.ts @@ -152,12 +152,14 @@ G6.registerNode( !checkTreeData(data) || (nodeDef.group && !usingGroups[nodeDef.group]) || (usingVars && - (data.input?.some((v) => usingVars?.[v] === undefined) || - data.output?.some((v) => usingVars?.[v] === undefined) || + (data.input?.some((v) => v && usingVars?.[v] === undefined) || + data.output?.some((v) => v && usingVars?.[v] === undefined) || nodeDef.args?.some( (v) => isExprType(v.type) && - parseExpr(data.args?.[v.name] ?? "").some((vv) => usingVars?.[vv] === undefined) + parseExpr(data.args?.[v.name] ?? "").some( + (vv) => vv && usingVars?.[vv] === undefined + ) ))) ) { classify = "Error"; diff --git a/src/contexts/workspace-context.ts b/src/contexts/workspace-context.ts index 4123baf..6198503 100644 --- a/src/contexts/workspace-context.ts +++ b/src/contexts/workspace-context.ts @@ -5,7 +5,14 @@ import * as fs from "fs"; import React from "react"; import { create } from "zustand"; import { NodeDef } from "../behavior3/src/behavior3"; -import { ImportDef, NodeModel, TreeGraphData, TreeModel, VarDef } from "../misc/b3type"; +import { + FileVarDecl, + ImportDef, + NodeModel, + TreeGraphData, + TreeModel, + VarDef, +} from "../misc/b3type"; import * as b3util from "../misc/b3util"; import { message } from "../misc/hooks"; import i18n from "../misc/i18n"; @@ -46,11 +53,7 @@ export class EditorStore { data: TreeModel; root: TreeGraphData; - declare: { - import: ImportDef[]; - subtree: ImportDef[]; - declvar: VarDef[]; - }; + declare: FileVarDecl; autoId: number = 1; dragSrcId?: string; @@ -354,11 +357,20 @@ export const useWorkspace = create((set, get) => ({ workspace.allFiles.forEach((file) => { const buildpath = buildDir + "/" + workspace.relative(file.path); const treeModel = b3util.createBuildData(file.path); - if (treeModel && treeModel.export === false) { + if (!treeModel) { + return; + } + if (treeModel.export === false) { console.log("skip:", buildpath); return; } console.log("build:", buildpath); + const declare: FileVarDecl = { + import: treeModel.import.map((v) => ({ path: v, vars: [], depends: [] })), + declvar: treeModel.declvar.map((v) => ({ name: v.name, desc: v.desc })), + subtree: [], + }; + b3util.refreshDeclare(treeModel, declare); if (!b3util.checkNodeData(treeModel?.root)) { hasError = true; } @@ -479,6 +491,10 @@ export const useWorkspace = create((set, get) => ({ set({ editors: workspace.editors }); workspace.updateFileMeta(editor); workspace.edit(editor.path, selectedNode); + + if (b3util.isNewVersion(editor.data.version)) { + message.warning(i18n.t("alertNewVersion", { version: editor.data.version })); + } } catch (error) { console.error(error); message.error(`invalid file: ${path}`); @@ -538,18 +554,7 @@ export const useWorkspace = create((set, get) => ({ if (!editor) { return; } - const vars: Set = new Set(editor.declare.declvar.slice()); - editor.declare.subtree = b3util.findSubtrees(editor.root).map((v) => ({ - path: v, - vars: [], - depends: [], - })); - b3util.loadVarDef(editor.declare.import).forEach((v) => vars.add(v)); - b3util.loadVarDef(editor.declare.subtree).forEach((v) => { - vars.add(v); - }); - b3util.updateUsingGroups(editor.data.group); - b3util.updateUsingVars(Array.from(vars)); + b3util.refreshDeclare(editor.data, editor.declare); }, save: () => { diff --git a/src/misc/b3type.ts b/src/misc/b3type.ts index 76f9b6e..430cc90 100644 --- a/src/misc/b3type.ts +++ b/src/misc/b3type.ts @@ -7,6 +7,8 @@ declare module "../behavior3/src/behavior3" { } } +export const VERSION = "1.8.0"; + export type NodeType = NodeDef["type"] | "Other" | "Error"; export type NodeArg = Exclude[number]; @@ -51,7 +53,14 @@ export interface ImportDef { }[]; } +export interface FileVarDecl { + import: ImportDef[]; + subtree: ImportDef[]; + declvar: VarDef[]; +} + export interface TreeModel { + version: string; name: string; desc?: string; export?: boolean; diff --git a/src/misc/b3util.ts b/src/misc/b3util.ts index b7c7369..ca658b2 100644 --- a/src/misc/b3util.ts +++ b/src/misc/b3util.ts @@ -1,6 +1,7 @@ import * as fs from "fs"; import { NodeDef } from "../behavior3/src/behavior3"; import { + FileVarDecl, ImportDef, isBoolType, isEnumType, @@ -14,6 +15,7 @@ import { TreeGraphData, TreeModel, VarDef, + VERSION, } from "./b3type"; import Path from "./path"; import { readJson, readTree } from "./util"; @@ -87,6 +89,16 @@ export const parseExpr = (expr: string) => { return result; }; +export const isNewVersion = (version: string) => { + const [major, minor, patch] = version.split(".").map(Number); + const [major2, minor2, patch2] = VERSION.split(".").map(Number); + return ( + major > major2 || + (major === major2 && minor > minor2) || + (major === major2 && minor === minor2 && patch > patch2) + ); +}; + export const isValidVariableName = (name: string) => { return /^[a-zA-Z_]/.test(name); }; @@ -286,6 +298,45 @@ export const checkNodeData = (data: NodeModel | null | undefined) => { let hasError = false; + if (conf.group) { + if (!usingGroups[conf.group]) { + error(data, `node group '${conf.group}' is not enabled`); + hasError = true; + } + } + + if (usingVars) { + if (data.input) { + for (const v of data.input) { + if (v && !usingVars[v]) { + error(data, `input variable '${v}' is not defined`); + hasError = true; + } + } + } + if (data.output) { + for (const v of data.output) { + if (v && !usingVars[v]) { + error(data, `output variable '${v}' is not defined`); + hasError = true; + } + } + } + + if (data.args && conf.args) { + for (const arg of conf.args) { + if (isExprType(arg.type)) { + for (const v of parseExpr(data.args[arg.name] ?? "")) { + if (v && !usingVars[v]) { + error(data, `expr variable '${arg.name}' is not defined`); + hasError = true; + } + } + } + } + } + } + if (conf.children !== undefined && conf.children !== -1) { const count = data.children?.length || 0; if (conf.children !== count) { @@ -712,6 +763,7 @@ export const createFileData = (data: TreeGraphData, includeSubtree?: boolean) => export const createNewTree = (path: string) => { const tree: TreeModel = { + version: VERSION, name: Path.basenameWithoutExt(path), firstid: 1, group: [], @@ -796,9 +848,9 @@ export const loadVarDef = (list: ImportDef[]) => { return Array.from(all); }; -export const findSubtrees = (data: TreeGraphData) => { +const findSubtrees = (data: NodeModel) => { const list: string[] = []; - const traverse = (v: TreeGraphData) => { + const traverse = (v: NodeModel) => { if (v.path) { list.push(v.path); } @@ -809,3 +861,18 @@ export const findSubtrees = (data: TreeGraphData) => { traverse(data); return list; }; + +export const refreshDeclare = (tree: TreeModel, declare: FileVarDecl) => { + const vars: Set = new Set(declare.declvar.slice()); + declare.subtree = findSubtrees(tree.root).map((v) => ({ + path: v, + vars: [], + depends: [], + })); + loadVarDef(declare.import).forEach((v) => vars.add(v)); + loadVarDef(declare.subtree).forEach((v) => { + vars.add(v); + }); + updateUsingGroups(tree.group); + updateUsingVars(Array.from(vars)); +}; diff --git a/src/misc/util.ts b/src/misc/util.ts index e2a3a45..d2a966f 100644 --- a/src/misc/util.ts +++ b/src/misc/util.ts @@ -1,5 +1,5 @@ import * as fs from "fs"; -import type { TreeModel } from "./b3type"; +import { VERSION, type TreeModel } from "./b3type"; export const readJson = (path: string) => { const str = fs.readFileSync(path, "utf-8"); @@ -9,6 +9,7 @@ export const readJson = (path: string) => { export const readTree = (path: string) => { const str = fs.readFileSync(path, "utf-8"); const data = JSON.parse(str) as TreeModel; + data.version = data.version ?? VERSION; data.firstid = data.firstid ?? 1; data.group = data.group || []; data.import = data.import || [];