"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const UE = require("ue"); const puerts_1 = require("puerts"); const ts = require("typescript"); const tsi = require("./TypeScriptInternal"); const uemeta = require("./UEMeta"); const cpp = require("cpp"); function getCustomSystem() { const customSystem = { args: [], newLine: '\n', useCaseSensitiveFileNames: true, write, readFile, writeFile, resolvePath: tsi.resolvePath, fileExists, directoryExists: tsi.directoryExists, createDirectory: tsi.createDirectory, getExecutingFilePath, getCurrentDirectory, getDirectories, readDirectory, exit, }; function fileExists(path) { let res = UE.FileSystemOperation.FileExists(path); //console.log(`${path} exists? ${res}`); return res; } function write(s) { console.log(s); } function readFile(path, encoding) { let data = puerts_1.$ref(undefined); const res = UE.FileSystemOperation.ReadFile(path, data); if (res) { return puerts_1.$unref(data); } else { console.warn("readFile: read file fail! path=" + path + ", stack:" + new Error().stack); return undefined; } } function writeFile(path, data, writeByteOrderMark) { throw new Error("forbiden!"); } /*function resolvePath(path: string): string { throw new Error("resolvePath no supported!"); }*/ function readDirectory(path, extensions, excludes, includes, depth) { //throw new Error("readDirectory no supported!"); return tsi.matchFiles(path, extensions, excludes, includes, true, getCurrentDirectory(), depth, tsi.getAccessibleFileSystemEntries, tsi.realpath); } function exit(exitCode) { throw new Error("exit with code:" + exitCode); } function getExecutingFilePath() { return getCurrentDirectory() + "Content/JavaScript/PuertsEditor/node_modules/typescript/lib/tsc.js"; } function getCurrentDirectory() { return UE.FileSystemOperation.GetCurrentDirectory(); } function getDirectories(path) { let result = []; let dirs = UE.FileSystemOperation.GetDirectories(path); for (var i = 0; i < dirs.Num(); i++) { result.push(dirs.Get(i)); } return result; } //for debug only /*return new Proxy({}, { get: function(target, name) { if (!(name in target)) { if (typeof name === 'string') { if (!(name in tgamejsSystem)) { return undefined; } let maybeFunc = tgamejsSystem[name]; if (typeof maybeFunc === 'function') { target[name] = function(...args: any[]) { const res = maybeFunc(...args); console.log("method:", name, ", args:", JSON.stringify(args), ",res:", res); return res; } } else { target[name] = tgamejsSystem[name]; } } } return target[name] } }) as ts.System;*/ return customSystem; } let customSystem = getCustomSystem(); if (!ts.sys) { let t = ts; t.sys = customSystem; } function logErrors(allDiagnostics) { allDiagnostics.forEach(diagnostic => { let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); if (diagnostic.file) { let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); console.error(` Error ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`); } else { console.error(` Error: ${message}`); } }); } const FunctionFlags = { FUNC_None: 0x00000000, FUNC_Final: 0x00000001, FUNC_RequiredAPI: 0x00000002, FUNC_BlueprintAuthorityOnly: 0x00000004, FUNC_BlueprintCosmetic: 0x00000008, // FUNC_ : 0x00000010, // unused. // FUNC_ : 0x00000020, // unused. FUNC_Net: 0x00000040, FUNC_NetReliable: 0x00000080, FUNC_NetRequest: 0x00000100, FUNC_Exec: 0x00000200, FUNC_Native: 0x00000400, FUNC_Event: 0x00000800, FUNC_NetResponse: 0x00001000, FUNC_Static: 0x00002000, FUNC_NetMulticast: 0x00004000, FUNC_UbergraphFunction: 0x00008000, FUNC_MulticastDelegate: 0x00010000, FUNC_Public: 0x00020000, FUNC_Private: 0x00040000, FUNC_Protected: 0x00080000, FUNC_Delegate: 0x00100000, FUNC_NetServer: 0x00200000, FUNC_HasOutParms: 0x00400000, FUNC_HasDefaults: 0x00800000, FUNC_NetClient: 0x01000000, FUNC_DLLImport: 0x02000000, FUNC_BlueprintCallable: 0x04000000, FUNC_BlueprintEvent: 0x08000000, FUNC_BlueprintPure: 0x10000000, FUNC_EditorOnly: 0x20000000, FUNC_Const: 0x40000000, FUNC_NetValidate: 0x80000000, FUNC_AllFlags: 0xFFFFFFFF, }; const PropertyFlags = { CPF_None: 0, CPF_Edit: 0x0000000000000001, CPF_ConstParm: 0x0000000000000002, CPF_BlueprintVisible: 0x0000000000000004, CPF_ExportObject: 0x0000000000000008, CPF_BlueprintReadOnly: 0x0000000000000010, CPF_Net: 0x0000000000000020, CPF_EditFixedSize: 0x0000000000000040, CPF_Parm: 0x0000000000000080, CPF_OutParm: 0x0000000000000100, CPF_ZeroConstructor: 0x0000000000000200, CPF_ReturnParm: 0x0000000000000400, CPF_DisableEditOnTemplate: 0x0000000000000800, //CPF_ : 0x0000000000001000, ///< CPF_Transient: 0x0000000000002000, CPF_Config: 0x0000000000004000, //CPF_ : 0x0000000000008000, ///< CPF_DisableEditOnInstance: 0x0000000000010000, CPF_EditConst: 0x0000000000020000, CPF_GlobalConfig: 0x0000000000040000, CPF_InstancedReference: 0x0000000000080000, //CPF_ : 0x0000000000100000, ///< CPF_DuplicateTransient: 0x0000000000200000, CPF_SubobjectReference: 0x0000000000400000, //CPF_ : 0x0000000000800000, ///< CPF_SaveGame: 0x0000000001000000, CPF_NoClear: 0x0000000002000000, //CPF_ : 0x0000000004000000, ///< CPF_ReferenceParm: 0x0000000008000000, CPF_BlueprintAssignable: 0x0000000010000000, CPF_Deprecated: 0x0000000020000000, CPF_IsPlainOldData: 0x0000000040000000, CPF_RepSkip: 0x0000000080000000, CPF_RepNotify: 0x0000000100000000, CPF_Interp: 0x0000000200000000, CPF_NonTransactional: 0x0000000400000000, CPF_EditorOnly: 0x0000000800000000, CPF_NoDestructor: 0x0000001000000000, //CPF_ : 0x0000002000000000, ///< CPF_AutoWeak: 0x0000004000000000, CPF_ContainsInstancedReference: 0x0000008000000000, CPF_AssetRegistrySearchable: 0x0000010000000000, CPF_SimpleDisplay: 0x0000020000000000, CPF_AdvancedDisplay: 0x0000040000000000, CPF_Protected: 0x0000080000000000, CPF_BlueprintCallable: 0x0000100000000000, CPF_BlueprintAuthorityOnly: 0x0000200000000000, CPF_TextExportTransient: 0x0000400000000000, CPF_NonPIEDuplicateTransient: 0x0000800000000000, CPF_ExposeOnSpawn: 0x0001000000000000, CPF_PersistentInstance: 0x0002000000000000, CPF_UObjectWrapper: 0x0004000000000000, CPF_HasGetValueTypeHash: 0x0008000000000000, CPF_NativeAccessSpecifierPublic: 0x0010000000000000, CPF_NativeAccessSpecifierProtected: 0x0020000000000000, CPF_NativeAccessSpecifierPrivate: 0x0040000000000000, CPF_SkipSerialization: 0x0080000000000000, }; const ELifetimeCondition = { "COND_InitialOnly": 1, "COND_OwnerOnly": 2, "COND_SkipOwner": 3, "COND_SimulatedOnly": 4, "COND_AutonomousOnly": 5, "COND_SimulatedOrPhysics": 6, "COND_InitialOrOwner": 7, "COND_Custom": 8, "COND_ReplayOrOwner": 9, "COND_ReplayOnly": 10, "COND_SimulatedOnlyNoReplay": 11, "COND_SimulatedOrPhysicsNoReplay": 12, "COND_SkipReplay": 13, "COND_Never": 15, }; function readAndParseConfigFile(configFilePath) { let readResult = ts.readConfigFile(configFilePath, customSystem.readFile); return ts.parseJsonConfigFileContent(readResult.config, { useCaseSensitiveFileNames: true, readDirectory: customSystem.readDirectory, fileExists: customSystem.fileExists, readFile: customSystem.readFile, trace: s => console.log(s) }, customSystem.getCurrentDirectory()); } function watch(configFilePath) { let { fileNames, options } = readAndParseConfigFile(configFilePath); console.log("start watch..", JSON.stringify({ fileNames: fileNames, options: options })); const versionsFilePath = tsi.getDirectoryPath(configFilePath) + "/ts_file_versions_info.json"; const fileVersions = {}; let beginTime = new Date().getTime(); fileNames.forEach(fileName => { fileVersions[fileName] = { version: UE.FileSystemOperation.FileMD5Hash(fileName), processed: false, isBP: false }; }); console.log("calc md5 using " + (new Date().getTime() - beginTime) + "ms"); function getDefaultLibLocation() { return tsi.getDirectoryPath(tsi.normalizePath(customSystem.getExecutingFilePath())); } const scriptSnapshotsCache = new Map(); // Create the language service host to allow the LS to communicate with the host const servicesHost = { getScriptFileNames: () => fileNames, getScriptVersion: fileName => { if (fileName in fileVersions) { return fileVersions[fileName] && fileVersions[fileName].version.toString(); } else { let md5 = UE.FileSystemOperation.FileMD5Hash(fileName); fileVersions[fileName] = { version: md5, processed: false }; return md5; } }, getScriptSnapshot: fileName => { if (!customSystem.fileExists(fileName)) { console.error("getScriptSnapshot: file not existed! path=" + fileName); return undefined; } if (!(fileName in fileVersions)) { fileVersions[fileName] = { version: UE.FileSystemOperation.FileMD5Hash(fileName), processed: false }; } if (!scriptSnapshotsCache.has(fileName)) { const sourceFile = customSystem.readFile(fileName); if (!sourceFile) { console.error("getScriptSnapshot: read file failed! path=" + fileName); return undefined; } scriptSnapshotsCache.set(fileName, { version: fileVersions[fileName].version, scriptSnapshot: ts.ScriptSnapshot.fromString(sourceFile) }); } let scriptSnapshotsInfo = scriptSnapshotsCache.get(fileName); if (scriptSnapshotsInfo.version != fileVersions[fileName].version) { const sourceFile = customSystem.readFile(fileName); if (!sourceFile) { console.error("getScriptSnapshot: read file failed! path=" + fileName); return undefined; } scriptSnapshotsInfo.version = fileVersions[fileName].version; scriptSnapshotsInfo.scriptSnapshot = ts.ScriptSnapshot.fromString(sourceFile); } //console.log("getScriptSnapshot:"+ fileName + ",in:" + new Error().stack) return scriptSnapshotsInfo.scriptSnapshot; }, getCurrentDirectory: customSystem.getCurrentDirectory, getCompilationSettings: () => options, getDefaultLibFileName: options => tsi.combinePaths(getDefaultLibLocation(), ts.getDefaultLibFileName(options)), fileExists: ts.sys.fileExists, readFile: ts.sys.readFile, readDirectory: ts.sys.readDirectory, directoryExists: ts.sys.directoryExists, getDirectories: ts.sys.getDirectories, }; let service = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); function getProgramFromService() { while (true) { try { return service.getProgram(); } catch (e) { console.error(e); } //异常了从新创建Language Service,有可能不断失败,UE的文件读取偶尔会失败,失败后ts增量编译会不断的在tryReuseStructureFromOldProgram那断言失败 service = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); } } let pendingBlueprintRefleshJobs = []; function isChildOf(a, b) { let baseTypes = a.getBaseTypes(); if (baseTypes.indexOf(b) >= 0) { return true; } for (let i = 0; i < baseTypes.length; ++i) { if (isChildOf(baseTypes[i], b)) { return true; } } return false; } function topologicalSort(classes) { const visited = new Set(); const result = []; function visit(node) { if (!visited.has(node)) { visited.add(node); for (const other of classes) { if (isChildOf(node.type, other.type)) { visit(other); } } result.push(node); } } for (const cls of classes) { visit(cls); } return result; } function refreshBlueprints() { if (pendingBlueprintRefleshJobs.length > 0) { pendingBlueprintRefleshJobs = topologicalSort(pendingBlueprintRefleshJobs); pendingBlueprintRefleshJobs.forEach(job => { job.op(); }); pendingBlueprintRefleshJobs = []; } } beginTime = new Date().getTime(); let program = getProgramFromService(); console.log("full compile using " + (new Date().getTime() - beginTime) + "ms"); let diagnostics = ts.getPreEmitDiagnostics(program); let restoredFileVersions = {}; var changed = false; const versionsFileExisted = customSystem.fileExists(versionsFilePath); if (versionsFileExisted) { try { restoredFileVersions = JSON.parse(customSystem.readFile(versionsFilePath)); console.log("restore versions from ", versionsFilePath); } catch { } } if (diagnostics.length > 0) { fileNames.forEach(fileName => { fileVersions[fileName] = restoredFileVersions[fileName] || fileVersions[fileName]; }); logErrors(diagnostics); } else { function getClassPathInfo(sourceFilePath) { let modulePath = undefined; let moduleFileName = undefined; if (sourceFilePath.endsWith(".ts")) { if (options.baseUrl && sourceFilePath.startsWith(options.baseUrl)) { moduleFileName = sourceFilePath.substr(options.baseUrl.length + 1); modulePath = tsi.getDirectoryPath(moduleFileName); moduleFileName = tsi.removeExtension(moduleFileName, ".ts"); } } return { moduleFileName, modulePath }; } fileNames.forEach(fileName => { if (!(fileName in restoredFileVersions) || restoredFileVersions[fileName].version != fileVersions[fileName].version || !restoredFileVersions[fileName].processed) { onSourceFileAddOrChange(fileName, false, program, true, false); changed = true; } else { fileVersions[fileName].processed = true; } if (fileName in restoredFileVersions) { fileVersions[fileName].isBP = restoredFileVersions[fileName].isBP; } }); fileNames.forEach(fileName => { if (versionsFileExisted && (!(fileName in restoredFileVersions))) return; if (versionsFileExisted && (!restoredFileVersions[fileName].isBP)) return; const { moduleFileName, modulePath } = getClassPathInfo(fileName); let BPExisted = false; if (moduleFileName) { BPExisted = UE.PEBlueprintAsset.Existed(moduleFileName, modulePath); } if (!versionsFileExisted || restoredFileVersions[fileName].version != fileVersions[fileName].version || !restoredFileVersions[fileName].processed || !BPExisted) { onSourceFileAddOrChange(fileName, false, program, false); changed = true; } else { fileVersions[fileName].processed = true; } }); refreshBlueprints(); if (changed) { UE.FileSystemOperation.WriteFile(versionsFilePath, JSON.stringify(fileVersions, null, 4)); } } var dirWatcher = new UE.PEDirectoryWatcher(); global.__dirWatcher = dirWatcher; //防止被释放? dirWatcher.OnChanged.Add((added, modified, removed) => { setTimeout(() => { var changed = false; let modifiedFiles = []; for (var i = 0; i < modified.Num(); i++) { modifiedFiles.push(modified.Get(i)); } let removedSet = new Set(); for (var i = 0; i < removed.Num(); i++) { removedSet.add(removed.Get(i)); } let addFiles = []; for (var i = 0; i < added.Num(); i++) { let fileName = added.Get(i); //remove and add is the same as modified if (removedSet.has(fileName)) { modifiedFiles.push(fileName); } else { addFiles.push(fileName); } } if (addFiles.length > 0) { onFileAdded(); changed = true; } modifiedFiles.forEach(fileName => { if (fileName in fileVersions) { let md5 = UE.FileSystemOperation.FileMD5Hash(fileName); if (md5 === fileVersions[fileName].version) { console.log(fileName + " md5 not changed, so skiped!"); } else { console.log(`${fileName} md5 from ${fileVersions[fileName].version} to ${md5}`); fileVersions[fileName].version = md5; onSourceFileAddOrChange(fileName, true); changed = true; } } }); refreshBlueprints(); if (changed) { console.log("versions saved to " + versionsFilePath); UE.FileSystemOperation.WriteFile(versionsFilePath, JSON.stringify(fileVersions, null, 4)); } }, 100); //延时100毫秒,防止因为读冲突而文件读取失败 }); dirWatcher.Watch(customSystem.getCurrentDirectory()); function onFileAdded() { let cmdLine = readAndParseConfigFile(configFilePath); let newFiles = []; cmdLine.fileNames.forEach(fileName => { if (!(fileName in fileVersions)) { console.log(`new file: ${fileName} ...`); newFiles.push(fileName); fileVersions[fileName] = { version: UE.FileSystemOperation.FileMD5Hash(fileName), processed: false }; } }); if (newFiles.length > 0) { fileNames = cmdLine.fileNames; options = cmdLine.options; program = getProgramFromService(); newFiles.forEach(fileName => onSourceFileAddOrChange(fileName, true, program)); } } function onSourceFileAddOrChange(sourceFilePath, reload, program, doEmitJs = true, doEmitBP = true) { if (!program) { let beginTime = new Date().getTime(); program = getProgramFromService(); console.log("incremental compile " + sourceFilePath + " using " + (new Date().getTime() - beginTime) + "ms"); } let sourceFile = program.getSourceFile(sourceFilePath); if (sourceFile) { const diagnostics = [ ...program.getSyntacticDiagnostics(sourceFile), ...program.getSemanticDiagnostics(sourceFile) ]; let checker = program.getTypeChecker(); if (diagnostics.length > 0) { logErrors(diagnostics); } else { if (doEmitBP) { fileVersions[sourceFilePath].isBP = false; } fileVersions[sourceFilePath].processed = true; if (!sourceFile.isDeclarationFile) { let emitOutput = service.getEmitOutput(sourceFilePath); if (!emitOutput.emitSkipped) { let modulePath = undefined; let moduleFileName = undefined; let jsSource = undefined; emitOutput.outputFiles.forEach(output => { if (doEmitJs) { console.log(`write ${output.name} ...`); UE.FileSystemOperation.WriteFile(output.name, output.text); } if (output.name.endsWith(".js") || output.name.endsWith(".mjs")) { jsSource = output.text; if (options.outDir && output.name.startsWith(options.outDir)) { moduleFileName = output.name.substr(options.outDir.length + 1); modulePath = tsi.getDirectoryPath(moduleFileName); moduleFileName = tsi.removeExtension(moduleFileName, output.name.endsWith(".js") ? ".js" : ".mjs"); } } }); if (moduleFileName && reload) { UE.FileSystemOperation.PuertsNotifyChange(moduleFileName, jsSource); } if (!doEmitBP) return; let foundType = undefined; let foundBaseTypeUClass = undefined; ts.forEachChild(sourceFile, (node) => { if (ts.isExportAssignment(node) && ts.isIdentifier(node.expression)) { const type = checker.getTypeAtLocation(node.expression); if (!type || !type.getSymbol()) return; if (type.getSymbol().getName() != tsi.getBaseFileName(moduleFileName)) { //console.error("type name must the same as file name!"); return; } let baseTypes = type.getBaseTypes(); if (!baseTypes || baseTypes.length != 1) return; let structOfType = getUClassOfType(baseTypes[0]); let baseTypeUClass = undefined; if (!structOfType) { return; } if (structOfType.GetClass().IsChildOf(UE.Class.StaticClass())) { baseTypeUClass = structOfType; } else { console.warn("do not support UStruct:" + checker.typeToString(type)); return; } if (baseTypeUClass) { if (isSubclassOf(type, "Subsystem")) { console.error("do not support Subsystem " + checker.typeToString(type)); return; } if (!baseTypeUClass.IsNative()) { let moduleNames = getModuleNames(baseTypes[0]); if (moduleNames.length > 1 && moduleNames[0] == 'ue') { console.error(`${checker.typeToString(type)} extends a blueprint`); return; } } foundType = type; foundBaseTypeUClass = baseTypeUClass; } else { console.warn("can not find base for " + checker.typeToString(type)); } } }); if (foundType && foundBaseTypeUClass) { fileVersions[sourceFilePath].isBP = true; //onBlueprintTypeAddOrChange(foundBaseTypeUClass, foundType, modulePath); pendingBlueprintRefleshJobs.push({ type: foundType, op: () => onBlueprintTypeAddOrChange(foundBaseTypeUClass, foundType, modulePath) }); } } } } function typeNameToString(node) { if (ts.isIdentifier(node)) { return node.text; } else { return node.right.text; } } function isSubclassOf(type, baseTypeName) { let baseTypes = type.getBaseTypes(); if (baseTypes.length != 1) return false; if (baseTypes[0].getSymbol().getName() == baseTypeName) { return true; } return isSubclassOf(baseTypes[0], baseTypeName); } function getUClassOfType(type) { if (!type) return undefined; let moduleNames = getModuleNames(type); if (moduleNames.length > 0 && moduleNames[0] == 'ue') { if (moduleNames.length == 1) { try { let jsCls = UE[type.symbol.getName()]; if (typeof jsCls.StaticClass == 'function') { return jsCls.StaticClass(); } } catch (e) { console.error(`load ue type [${type.symbol.getName()}], throw: ${e}`); } } else if (moduleNames.length == 2) { let classPath = '/' + moduleNames[1] + '.' + type.symbol.getName(); return UE.Field.Load(classPath, true); } } else if (type.symbol && type.symbol.valueDeclaration) { //eturn undefined; let baseTypes = type.getBaseTypes(); if (!baseTypes || baseTypes.length != 1) return undefined; let baseTypeUClass = getUClassOfType(baseTypes[0]); if (!baseTypeUClass) return undefined; //console.error("modulePath:", getModulePath(type.symbol.valueDeclaration.getSourceFile().fileName)); let sourceFile = type.symbol.valueDeclaration.getSourceFile(); let sourceFileName; program.emit(sourceFile, writeFile, undefined, false, undefined); function writeFile(fileName, text, writeByteOrderMark) { if (fileName.endsWith('.js')) { sourceFileName = tsi.removeExtension(fileName, '.js'); } } if (tsi.getBaseFileName(sourceFileName) != type.symbol.getName()) { console.error("type name must the same as file name!"); return undefined; } if (options.outDir && sourceFileName.startsWith(options.outDir)) { let moduleFileName = sourceFileName.substr(options.outDir.length + 1); let modulePath = tsi.getDirectoryPath(moduleFileName); let bp = new UE.PEBlueprintAsset(); bp.LoadOrCreate(type.getSymbol().getName(), modulePath, baseTypeUClass, 0, 0); bp.Save(); return bp.GeneratedClass; } } } function getSymbolTypeNode(symbol) { if (symbol.valueDeclaration) { for (var i = symbol.valueDeclaration.getChildCount() - 1; i >= 0; i--) { var child = symbol.valueDeclaration.getChildAt(i); if (child.kind == ts.SyntaxKind.TypeReference) { return child; } } } } function tsTypeToPinType(type, node) { if (!type) return undefined; try { let typeNode = checker.typeToTypeNode(type, undefined, undefined); //console.log(checker.typeToString(type), tds) if (ts.isTypeReferenceNode(typeNode) && type.symbol) { let typeName = type.symbol.getName(); if (typeName == 'BigInt') { let category = "int64"; let pinType = new UE.PEGraphPinType(category, undefined, UE.EPinContainerType.None, false, false); return { pinType: pinType }; } if (!typeNode.typeArguments || typeNode.typeArguments.length == 0) { let category = "object"; let uclass = getUClassOfType(type); if (!uclass) { let uenum = UE.Enum.Find(type.symbol.getName()); if (uenum) { return { pinType: new UE.PEGraphPinType("byte", uenum, UE.EPinContainerType.None, false, false) }; } console.warn("can not find type of " + typeName); return undefined; } let pinType = new UE.PEGraphPinType(category, uclass, UE.EPinContainerType.None, false, false); return { pinType: pinType }; } else { //TArray, TSet, TMap let typeRef = type; var children = []; let typeArguments = typeRef.typeArguments || typeRef.aliasTypeArguments; if (typeRef.aliasTypeArguments && typeRef.aliasSymbol) { typeName = typeRef.aliasSymbol.getName(); } if (!typeArguments) { console.warn("can not find type arguments of " + node.getFullText()); return undefined; } if (node) { node.forEachChild(child => { children.push(child); }); } let result = tsTypeToPinType(typeArguments[0], children[1]); if (!result || result.pinType.PinContainerType != UE.EPinContainerType.None && typeName != '$Ref' && typeName != '$InRef') { console.warn("can not find pin type of typeArguments[0] " + typeName); return undefined; } if (children[1]) { postProcessPinType(children[1], result.pinType, false); } if (typeName == 'TArray' || typeName == 'TSet') { result.pinType.PinContainerType = typeName == 'TArray' ? UE.EPinContainerType.Array : UE.EPinContainerType.Set; return result; } else if (typeName == 'TSubclassOf') { let category = "class"; result.pinType.PinCategory = category; return result; } else if (typeName == 'TSoftObjectPtr') { let category = "softobject"; result.pinType.PinCategory = category; return result; } else if (typeName == 'TSoftClassPtr') { let category = "softclass"; result.pinType.PinCategory = category; return result; } else if (typeName == '$Ref') { result.pinType.bIsReference = true; return result; } else if (typeName == '$InRef') { result.pinType.bIsReference = true; result.pinType.bIn = true; return result; } else if (typeName == 'TMap') { let valuePinType = tsTypeToPinType(typeArguments[1], undefined); if (!valuePinType || valuePinType.pinType.PinContainerType != UE.EPinContainerType.None) { console.warn("can not find pin type of typeArguments[1] " + typeName); return undefined; } if (children[2]) { postProcessPinType(children[2], valuePinType.pinType, false); } result.pinType.PinContainerType = UE.EPinContainerType.Map; result.pinValueType = new UE.PEGraphTerminalType(valuePinType.pinType.PinCategory, valuePinType.pinType.PinSubCategoryObject); return result; } else { console.warn("not support generic type: " + typeName); return undefined; } } } else { //"bool" | "class" | "int64" | "string" | "object" | "struct" | "float"; let category; switch (typeNode.kind) { case ts.SyntaxKind.NumberKeyword: category = "float"; break; case ts.SyntaxKind.StringKeyword: category = 'string'; break; case ts.SyntaxKind.BigIntKeyword: category = 'int64'; break; case ts.SyntaxKind.BooleanKeyword: category = 'bool'; break; default: console.warn("not support kind: " + typeNode.kind); return undefined; } let pinType = new UE.PEGraphPinType(category, undefined, UE.EPinContainerType.None, false, false); return { pinType: pinType }; } } catch (e) { console.error(e.stack || e); return undefined; } } function manualSkip(valueDeclaration) { const commentRanges = ts.getLeadingCommentRanges(sourceFile.getFullText(), valueDeclaration.getFullStart()); return !!(commentRanges && commentRanges.find(r => sourceFile.getFullText().slice(r.pos, r.end).indexOf("@no-blueprint") > 0)) || hasDecorator(valueDeclaration, "no_blueprint"); } function tryGetAnnotation(valueDeclaration, key, leading) { const commentRanges = (leading ? ts.getLeadingCommentRanges : ts.getTrailingCommentRanges)(sourceFile.getFullText(), valueDeclaration.getFullStart() + (leading ? 0 : valueDeclaration.getFullWidth())); if (commentRanges) { let ret; commentRanges.forEach(r => { let m = sourceFile.getFullText().slice(r.pos, r.end).match(new RegExp(`@${key}:([^*]*)`)); if (m) { ret = m[1].trim(); } }); return ret; } } function postProcessPinType(valueDeclaration, pinType, leading) { if (pinType.PinContainerType == UE.EPinContainerType.None) { let pc = pinType.PinCategory; if (pc === "float") { let cppType = tryGetAnnotation(valueDeclaration, "cpp", leading); if (cppType === "int" || cppType === "byte") { pinType.PinCategory = cppType; } } else if (pc === "string") { let cppType = tryGetAnnotation(valueDeclaration, "cpp", leading); if (cppType === "name" || cppType === "text") { pinType.PinCategory = cppType; } } } } function getFlagsValue(str, flagsDef) { if (!str) return 0; return str.split("|").map(x => x.trim()).map(x => x in flagsDef ? flagsDef[x] : 0).reduce((x, y) => x | y); } function getDecoratorFlagsValue(valueDeclaration, posfix, flagsDef) { if (valueDeclaration && valueDeclaration.decorators) { let decorators = valueDeclaration.decorators; let ret = 0n; decorators.forEach((decorator, index) => { let expression = decorator.expression; if (ts.isCallExpression(expression)) { if (expression.expression.getFullText() == posfix || expression.expression.getFullText().endsWith('.' + posfix)) { expression.arguments.forEach((value, index) => { let e = value.getFullText().split("|").map(x => x.trim().replace(/^.*[\.]/, '')) .map(x => x in flagsDef ? BigInt(flagsDef[x]) : 0n) .reduce((x, y) => BigInt(x) | BigInt(y)); ret = ret | e; }); } } }); return ret; } else { return 0n; } } function hasDecorator(valueDeclaration, posfix) { let ret = false; if (valueDeclaration && valueDeclaration.decorators) { let decorators = valueDeclaration.decorators; decorators.forEach((decorator, index) => { let expression = decorator.expression; if (ts.isCallExpression(expression)) { if (expression.expression.getFullText() == posfix || expression.expression.getFullText().endsWith('.' + posfix)) { ret = true; } } }); } return ret; } function onBlueprintTypeAddOrChange(baseTypeUClass, type, modulePath) { console.log(`gen blueprint for ${type.getSymbol().getName()}, path: ${modulePath}`); let lsFunctionLibrary = baseTypeUClass && baseTypeUClass.GetName() === "BlueprintFunctionLibrary"; let bp = new UE.PEBlueprintAsset(); bp.LoadOrCreateWithMetaData(type.getSymbol().getName(), modulePath, baseTypeUClass, 0, 0, uemeta.compileClassMetaData(type)); let hasConstructor = false; let properties = []; type.symbol.valueDeclaration.forEachChild(x => { if (ts.isMethodDeclaration(x) && !manualSkip(x)) { let isStatic = !!(ts.getCombinedModifierFlags(x) & ts.ModifierFlags.Static); if (isStatic && !lsFunctionLibrary) { console.warn(`do not support static function [${x.name.getText()}]`); return; } if (!isStatic && lsFunctionLibrary) { console.warn(`do not support non-static function [${x.name.getText()}] in BlueprintFunctionLibrary`); return; } if (x.name.getText() === 'ReceiveInit') { if (baseTypeUClass == UE.GameInstance.StaticClass() || baseTypeUClass.IsChildOf(UE.GameInstance.StaticClass())) { console.warn(`do not support override GameInstance.ReceiveInit in ${type.getSymbol().getName()}`); return; } } properties.push(checker.getSymbolAtLocation(x.name)); } else if (ts.isPropertyDeclaration(x) && !manualSkip(x)) { let isStatic = !!(ts.getCombinedModifierFlags(x) & ts.ModifierFlags.Static); if (isStatic) { console.warn("static property:" + x.name.getText() + ' not support'); return; } properties.push(checker.getSymbolAtLocation(x.name)); } }); let attachments = UE.NewMap(UE.BuiltinName, UE.BuiltinName); properties .filter(x => ts.isClassDeclaration(x.valueDeclaration.parent) && checker.getSymbolAtLocation(x.valueDeclaration.parent.name) == type.symbol) .forEach((symbol) => { if (ts.isMethodDeclaration(symbol.valueDeclaration)) { if (symbol.getName() === 'Constructor') { hasConstructor = true; return; } let methodType = checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration); let signatures = checker.getSignaturesOfType(methodType, ts.SignatureKind.Call); if (!signatures) { console.warn(`can not find signature for ${symbol.getName()} `); return; } if (signatures.length != 1) { console.warn(`find more than one signature for ${symbol.getName()} `); return; } let signature = signatures[0]; for (var i = 0; i < signature.parameters.length; i++) { let paramType = checker.getTypeOfSymbolAtLocation(signature.parameters[i], signature.parameters[i].valueDeclaration); let paramPinType = tsTypeToPinType(paramType, getSymbolTypeNode(signature.parameters[i])); if (!paramPinType) { console.warn(symbol.getName() + " of " + checker.typeToString(type) + " has not supported parameter!"); bp.ClearParameter(); return; } postProcessPinType(signature.parameters[i].valueDeclaration, paramPinType.pinType, false); // bp.AddParameter(signature.parameters[i].getName(), paramPinType.pinType, paramPinType.pinValueType); bp.AddParameterWithMetaData(signature.parameters[i].getName(), paramPinType.pinType, paramPinType.pinValueType, uemeta.compileParamMetaData(signature.parameters[i])); } //console.log("add function", symbol.getName()); let sflags = tryGetAnnotation(symbol.valueDeclaration, "flags", true); let flags = getFlagsValue(sflags, FunctionFlags); let clearFlags = 0; if (symbol.valueDeclaration && symbol.valueDeclaration.decorators) { flags |= Number(getDecoratorFlagsValue(symbol.valueDeclaration, "flags", FunctionFlags)); flags |= Number(getDecoratorFlagsValue(symbol.valueDeclaration, "set_flags", FunctionFlags)); clearFlags = Number(getDecoratorFlagsValue(symbol.valueDeclaration, "clear_flags", FunctionFlags)); } if (symbol.valueDeclaration.type && (ts.SyntaxKind.VoidKeyword === symbol.valueDeclaration.type.kind)) { // bp.AddFunction(symbol.getName(), true, undefined, undefined, flags, clearFlags); bp.AddFunctionWithMetaData(symbol.getName(), true, undefined, undefined, flags, clearFlags, uemeta.compileFunctionMetaData(symbol)); } else { let returnType = signature.getReturnType(); let resultPinType = tsTypeToPinType(returnType, getSymbolTypeNode(symbol)); if (!resultPinType) { console.warn(symbol.getName() + " of " + checker.typeToString(type) + " has not supported return type!"); bp.ClearParameter(); return; } postProcessPinType(symbol.valueDeclaration, resultPinType.pinType, true); // bp.AddFunction(symbol.getName(), false, resultPinType.pinType, resultPinType.pinValueType, flags, clearFlags); bp.AddFunctionWithMetaData(symbol.getName(), false, resultPinType.pinType, resultPinType.pinValueType, flags, clearFlags, uemeta.compileFunctionMetaData(symbol)); } bp.ClearParameter(); } else { let propType = checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration); let propPinType = tsTypeToPinType(propType, getSymbolTypeNode(symbol)); if (!propPinType) { console.warn(symbol.getName() + " of " + checker.typeToString(type) + " not support!"); } else { postProcessPinType(symbol.valueDeclaration, propPinType.pinType, true); //console.log("add member variable", symbol.getName()); let sflags = tryGetAnnotation(symbol.valueDeclaration, "flags", true); let flags = BigInt(getFlagsValue(sflags, PropertyFlags)); let cond = 0; if (symbol.valueDeclaration && symbol.valueDeclaration.decorators) { cond = Number(getDecoratorFlagsValue(symbol.valueDeclaration, "condition", ELifetimeCondition)); if (cond != 0) { flags = flags | BigInt(PropertyFlags.CPF_Net); } flags = flags | getDecoratorFlagsValue(symbol.valueDeclaration, "flags", PropertyFlags); symbol.valueDeclaration.decorators.forEach((decorator) => { let expression = decorator.expression; if (ts.isCallExpression(expression)) { if (expression.expression.getFullText().endsWith("uproperty.attach")) { expression.arguments.forEach((value) => { attachments.Add(symbol.getName(), value.getFullText().slice(1, -1)); }); } } }); } // Invalid code, only recognizes UPROPERTY UMETA, causing the DisableEditOnInstance flag to be added every time. // if (!hasDecorator(symbol.valueDeclaration, "edit_on_instance")) { // flags = flags | BigInt(PropertyFlags.CPF_DisableEditOnInstance); // } // bp.AddMemberVariable(symbol.getName(), propPinType.pinType, propPinType.pinValueType, Number(flags & 0xffffffffn), Number(flags >> 32n), cond); bp.AddMemberVariableWithMetaData(symbol.getName(), propPinType.pinType, propPinType.pinValueType, Number(flags & 0xffffffffn), Number(flags >> 32n), cond, uemeta.compilePropertyMetaData(symbol)); } } }); bp.RemoveNotExistedComponent(); bp.RemoveNotExistedMemberVariable(); bp.RemoveNotExistedFunction(); bp.SetupAttachments(attachments); bp.HasConstructor = hasConstructor; bp.Save(); } function getModuleNames(type) { let ret = []; if (type.symbol && type.symbol.valueDeclaration && type.symbol.valueDeclaration.parent && ts.isModuleBlock(type.symbol.valueDeclaration.parent)) { let moduleBody = type.symbol.valueDeclaration.parent; while (moduleBody) { let moduleDeclaration = moduleBody.parent; let nameOfModule = undefined; while (moduleDeclaration) { let ns = moduleDeclaration.name.text; ns = ns.startsWith("$") ? ns.substring(1) : ns; nameOfModule = nameOfModule ? (ns + '/' + nameOfModule) : ns; if (ts.isModuleDeclaration(moduleDeclaration.parent)) { moduleDeclaration = moduleDeclaration.parent; } else { break; } } ret.push(nameOfModule); if (moduleDeclaration && ts.isModuleBlock(moduleDeclaration.parent)) { moduleBody = moduleDeclaration.parent; } else { break; } } } return ret.reverse(); } } } //function getOwnEmitOutputFilePath(fileName: string) { function getModulePath(fileName) { const compilerOptions = options; let emitOutputFilePathWithoutExtension; if (compilerOptions.outDir) { emitOutputFilePathWithoutExtension = tsi.removeFileExtension(tsi.getSourceFilePathInNewDir(fileName, customSystem.getCurrentDirectory(), program.getCommonSourceDirectory(), options.outDir)); } else { emitOutputFilePathWithoutExtension = tsi.removeFileExtension(fileName); } return emitOutputFilePathWithoutExtension; } function list(pattern) { var re = new RegExp(pattern ? pattern : '.*'); console.log(`id\t\t\t\t\t\t\t\t\tprocessed\tisBP\tpath`); for (var key in fileVersions) { var value = fileVersions[key]; if (!pattern || re.test(key)) { console.log(`${value.version}\t${!!value.processed}\t\t${!!value.isBP}\t${key}`); } } } function compile(id) { for (var key in fileVersions) { var value = fileVersions[key]; if (value.version === id.trim()) { console.log(`compiling ${key} ...`); onSourceFileAddOrChange(key, true); } } refreshBlueprints(); } function dispatchCmd(cmd, args) { if (cmd == 'ls') { list(args); } else if (cmd == 'compile') { compile(args); } else { console.error(`unknow command for Puerts ${cmd}`); } } cpp.FPuertsEditorModule.SetCmdCallback(dispatchCmd); } watch(customSystem.getCurrentDirectory() + "tsconfig.json"); //# sourceMappingURL=CodeAnalyze.js.map